本文主要是介绍《数据结构》第2章 表(C语言描述),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
2.1线性表
数据结构指的是数据元素及数据元素之间的相互关系,包含下面三方面的内容:
其中,线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。
线性表是一个线性结构,它是一个含有n≥0个结点的有限序列,对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。
特征:
- 集合中必存在唯一的一个“第一元素”;
- 集合中必存在唯一的一个 “最后元素” ;
- 除最后一个元素之外,均有 唯一的后继(后件);
- 除第一个元素之外,均有 唯一的前驱(前件);
线性表作为一种基本的数据结构类型,在计算机存储器的映像(或表示)一般有两种形式:
- 顺序映像—即我们常用的数组;
- 链式映像—即我们常用的链表。
1)顺序存储结构的特点
- 逻辑上相邻的元素 A ( i ) A ( i + 1 ) A(i) A(i+1) A(i)A(i+1),其存储位置也是相邻的;
- 对数据元素 A ( i ) A(i) A(i)的存取为随机存取或地址存取。
- 存储密度高。存储密度D=(数据结构中的元素所占存储空间)/(整个数据结构所占空间)
2)顺序存储结构的不足
对表的插入和删除等运算的时间复杂度较差。
3)顺序存储结构的表示
在C语言中,一维数组的元素也是存放于一片连续的存储空间中,故可借助于C语言中一维数组类型来描述线性表的存储结构,即
#define N 100
typedef int data_t;//这样定义的目的是为了代码便于维护,如果下次表中数据类型为char,修改比较方便;
typedef struct
{ data_t data[N];//表的存储空间 int last;//当前表尾指针,对我们对数据的定位起到很大的作用
}sqlist_t,*sqlink_t;//顺序表类型
指针 L 指向一个顺序表,我们的数据{a0,a1,a2… last};
sqlink_t L;
L = (sqlink_t *)malloc(sizeof(sqlink_t));
这里我们定义的 int last ,可以表示{ } 、{ a0 }、{a0,a1}、{a0,a1…a99};ai可以表示为 L->data[i] (0<=i<=L->last),一般情况下,0 < L->last < N,如果是空表{ },此时L->last = -1;
下面我们通过一个实例来看线性表基本算法的相关算法如何使用:
seqlist.h
#ifndef _SEQ_LIST_H_
#define _SEQ_LIST_H_
typedef int data_t;
#define MAX 100
typedef struct {
data_t data[MAX];
/*****************************
*pointer to the position
* in the array 'data' where
* stores the last element of
* the list
*****************************/
int last;
} seqlist_t;
seqlist_t *CreateEmptySqlist();
void DestroySqlist(seqlist_t *list);
void ClearSqlist(seqlist_t *list);
int EmptySqlist(seqlist_t *list);
int FullSqlist(seqlist_t *list);
int LengthSqlist(seqlist_t *list);
int GetSqlist(seqlist_t *list, int at, data_t *x);
int SetSqlist(seqlist_t *list, int at, data_t x);
int InsertSqlist(seqlist_t *list, int at, data_t x);
int DeleteSqlist(seqlist_t *list, int at);
#endif /* _SEQ_LIST_H_ */
seqlist.c
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h" /**************************************************create a list and init it as empty *Input : void *Output: void *Return: new list, NULL when failed *************************************************/
seqlist_t *CreateEmptySqlist()
{seqlist_t *list; list = (seqlist_t *)malloc(sizeof(seqlist_t)); if(list != NULL) {list->last = -1;//list->last初始化,空表中,list-last = -1; } return list;
} /**************************************************destroy a list *Input : the list to be destroied. *Output: void *Return: void *************************************************/
void DestroySqlist(seqlist_t *list)
{if (list == NULL) return ; free(list); return;
} /**************************************************clear the list and reset it as empty *Input : the list to be cleared. *Output: void *Return: void *************************************************/
void ClearSqlist(seqlist_t *list)
{if (list == NULL) return ; list->last = -1;//清空线性表
} /************************************************ *judge if the list is empty *Input: the list to be tested. *Output: void *Return: * 1: list is empty * 0: not * -1: error, e.g. the list is invalid ************************************************/
int EmptySqlist(seqlist_t *list)
{ if (list == NULL) return -1; if(list->last == -1)//空表的标志 return 1; else return 0;
} /************************************************ *judge if the list is full *Input : the list to be tested. *Output: void *Return: * 1 : list is full * 0 : not * -1: error ************************************************/
int FullSqlist(seqlist_t *list)
{if (list == NULL) return -1; if (MAX - 1 == list->last) return 1; else return 0;
} /************************************************ *get length of the list *Input : the list to be tested. *Output: void *Return: * 0: length of the list; * -1 : means error ************************************************/
int LengthSqlist(seqlist_t *list)
{if (list == NULL) return -1; else return (list->last + 1);
} /************************************************ *get data of element at specified position *Input : * list : the list to be operated. * at : the position where to get the element at, * position index starts from zero. *Output: x : the data value returned *Return: * 0 : success; * -1: error, e.g. list is invalid; 'at' extends * the range of the list ************************************************/
int GetSqlist(seqlist_t *list, int at, data_t *x) //data_t *x为出参
{if(list == NULL || at < 0 || at > list->last) return -1; *x = list->data[at]; return 0;
} /***************************************************set/update data of element at specified position *Input : * list :the list to be operated. * at :the position at where to set the element,position index starts from zero * x : the new data value *Output: void *Return: * 0 : success; * -1: error, e.g. list is invalid; 'at' extends the range of the list *************************************************/
int SetSqlist(seqlist_t *list, int at, data_t x)//data_t x为入参
{if(list == NULL || at < 0 || (at > list->last)) return -1; list->data[at] = x; return 0;
} /************************************************ *Insert element at the specified position. If the "at" exceed the *upper limit of the list, append the data at the end of the list. *e.g. insert a new data into a { }. *Input : * list : the list to be operated. * at : the position at which to insert the new element,position index starts from zero. * x : the data to be inserted *Output: void *Return: * 0 : success; * <0: error ************************************************/
int InsertSqlist(seqlist_t *list, int at, data_t x)
{int j; if(list == NULL || at < 0 || FullSqlist(list))if(at > list->last)//此情况比较特殊,如表中长度是50,却要插在60前面,我们这里将其插在50后面,即at = list->last + 1 {at = list->last + 1;//若是空表{},list->last为-1,此时将其放在第0位; } else { for(j = list->last; j >= at; j--) {list->data[j+1] = list->data[j]; } } list->data[at] = x; list->last++;//list->last要+1 return 0;
} /************************************************* *delete the element by the position *Input : * list : the list to be operated. * at : the position at which to delete the element, *position index starts from zero *Output : void *Return : * 0 : success; * !0 : error *************************************************/
int DeleteSqlist(seqlist_t *list, int at)
{int i; if(list == NULL || at < 0 || (at > list->last)) return -1; for(i = at + 1; i <= list->last;i++) list->data[i-1] = list->data[i]; list->last--; return 0;
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include "seqlist.h" void iterate_list(seqlist_t *list)
{int i; printf("list.last = %d", list->last); for (i = -1; i < list->last;) {printf("%d,", list->data[++i]); } if (LengthSqlist(list) > 0) printf("\b}\n"); else printf("}\n");
} int main(int argc, const char *argv[])
{int i; int length; data_t a[10] = {2,4,6,8,10,12,14,16,18,20}; data_t x; seqlist_t *list; list = CreateEmptySqlist(); if (list == NULL) return -1; for(i = 0; i < 10;i++) {if((InsertSqlist(list,i,a[i])) < 0) break; } iterate_list(list); length = LengthSqlist(list); printf("The length of the list is %d\n",length); GetSqlist(list,4,&x); printf("The NO.4 data of the list is %d\n",x); SetSqlist(list,4,5); GetSqlist(list,4,&x); printf("After updataing! The NO.4 data of the list is %d\n",x); printf("Delete data[4]!\n"); DeleteSqlist(list,4); GetSqlist(list,4,&x); printf("Now data[4] = %d\n",x); printf("Now the legth of the list is %d\n",LengthSqlist(list)); ClearSqlist(list); if(EmptySqlist(list)) printf("The list is empty!\n"); printf("Now the legth of the list is %d\n",LengthSqlist(list)); DestroySqlist(list); printf("The list is destroyed!\n"); return 0;
}
makefile
.PHONY : clean
OBJ = test
OBJS = seqlist.o main.o
CFLAGS = -c -g -Wall
CC = gcc
$(OBJ):$(OBJS) @$(CC) -o test -g $(OBJS)
main.o:main.c seqlist.h @$(CC) $(CFLAGS) main.c
seqlist.o:seqlist.c seqlist.h @$(CC) $(CFLAGS) seqlist.c
clean: rm -rf *.o
执行结果如下:
2.2链表
2.2.1单向链表
线性表存储结构分为顺序存储、链式存储。
链表就是典型的链式存储,将线性表L = (a0,a1,a2,…an-1)中个元素分布在存储器的不同存储块,成为结点(Node),通过地址或指针建立他们之间的练习,所得到的存储结构为链表结构。
2.2.1.1节点类型描述:
typedef struct node_t
{data_t data; //节点的数据域struct node_t *next;//节点的后继指针域
}linknode_t,*linklist_t;
也可这样表示:
struct node_t
{data_t data; struct node_t *next;
}
typedef struct node_t linknode_t;
typedef struct node_t *linklist_t;
若说明
linknode_t A;
linklist_t p = &A;
则结构变量A为所描述的节点,而指针变量P为指向此类型节点的指针(p的值为节点的地址);
这样看来 linknode_t linklist_t 的作用是一样的,那为什么我们要定义两个数据类型(同一种)呢?主要为了代码的可读性,我们要求标识符要望文识义,便于理解;
1、linknode_t *pnode 指向一个节点;
2、linklist_t list 指向一个整体
2.2.1.2头结点 head
我们在前篇提到的顺序存储线性表,如何表达一个空表{ },是通过list->last = -1来表现的,所谓的空表就是数据域为NULL,而我们的链表有数据域和指针域,我们如何表现空链表呢?这时,就引入了头结点的概念,头结点和其他节点数据类型一样,只是数据域为NULL,head->next = NULL,下面我们看一个创建空链表的函数,如何利用头结点来创建一个空链表:
linklist_t CreateEmptyLinklist()
{linklist_t list;list = (linklist_t)malloc(sizeof(linknode_t));if (NULL != list) {list->next = NULL;}return list;
}
只要头结点,链表就还在!
2.2.1.3链表基本运算的相关算法
链表的运算除了上面的创建空链表,还有数据的插入,删除,查找等函数,链表的运算有各种实现方法,如何写出一个高效的,封装性较好的函数是我们要考虑的,比如数据插入函数,我们就要尽可能考虑所有能出现的结果,比如:1)如果需插入数据的链表是个空表;2)所插入的位置超过了链表的长度;如果我们的函数能包含所有能出现的情况,不仅能大大提高我们的开发效率,也会减少代码的错误率。下面,我们来看看下面的这个链表的插入函数的实现:
int InsertLinklist(linklist_t list, int at, data_t x)
{linknode_t *node_prev, *node_at, *node_new;int pos_at;int found = 0;if (NULL == list) return -1;/* at must >= 0 */if (at < 0) return -1;/*第一步、分配空间*/node_new = malloc(sizeof(linknode_t));if (NULL == node_new) {return -1;}node_new->data = x; /* assigned value */node_new->next = NULL; /*节点如果插入超过链表长度的位置,会接到尾节点后面,这样,node_new成了尾节点,node_new->next = NULL *//*第二步、定位*/node_prev = list;//跟随指针,帮助我们更好的定位node_at = list->next; //遍历指针pos_at = 0;while (NULL != node_at) {if (pos_at == at){found = 1; //找到正确的位置,跳出循环break; }/* move to the next pos_at */node_prev = node_at; //跟随指针先跳到遍历指针的位置node_at = node_at->next;//遍历指针跳到下一个节点的位置pos_at++;}/*第三步、插入*/ if (found) {/* found = 1,找到正确的位置,插入 */node_new->next = node_at;//插入的节点next指向node_atnode_prev->next = node_new;//插入节点的前一个节点} else {/*若是没找到正确的位置,即所插入位置超越了链表的长度,则接到尾节点的后面,同样,这样适用于{ }即空链表,这样我们可以建立一个空链表,利用这个函数,实现链表的初始化*/node_prev->next = node_new;}
这个插入函数可利用性就非常高。
下面讲一个完整链表代码贴出:
listlink.h
#ifndef _LNK_LIST_H_
#define _LNK_LIST_H_typedef int data_t;
typedef struct node_t {data_t data;struct node_t *next;
} linknode_t, *linklist_t;linklist_t CreateEmptyLinklist();
void DestroyLinklist(linklist_t list);
void ClearLinklist(linklist_t list);
int EmptyLinklist(linklist_t list);
int LengthLinklist(linklist_t list);
int GetLinklist(linklist_t list, int at, data_t *x);int SetLinklist(linklist_t list, int at, data_t x);
int InsertLinklist(linklist_t list, int at, data_t x);
int DeleteLinklist(linklist_t list, int at);
linklist_t ReverseLinklist(linklist_t list);
#endif /* _LNK_LIST_H_ */
linklist.c
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"linklist_t CreateEmptyLinklist()
{linklist_t list;list = (linklist_t)malloc(sizeof(linknode_t));if (NULL != list) {list->next = NULL;}return list;
}void DestroyLinklist(linklist_t list)
{if (NULL != list) {ClearLinklist(list);free(list);}
}void ClearLinklist(linklist_t list)
{linknode_t *node; /* pointer to the node to be removed */if (NULL == list) return;while (NULL != list->next) {node = list->next;list->next = node->next;free(node);}return;
}int LengthLinklist(linklist_t list)
{int len = 0;linknode_t *node; //iterate pointerif (NULL == list) return -1;node = list->next; // node points to the first data nodewhile (NULL != node) {len++;node = node->next;}return len;
}int EmptyLinklist(linklist_t list)
{if (NULL != list) {if (NULL == list->next) {return 1;} else {return 0;}} else {return -1;}
}int GetLinklist(linklist_t list, int at, data_t *x)
{linknode_t *node; /* used for iteration */int pos; /* used for iteration and compare with */if (NULL == list) return -1;/* at must >= 0 */if (at < 0) return -1;/* start from the first element */node = list->next;pos = 0;while (NULL != node) {if (at == pos) {if (NULL != x) {*x = node->data;}return 0; }/* move to the next */node = node->next;pos++;}return -1;
}int SetLinklist(linklist_t list, int at, data_t x)
{linknode_t *node; /* used for iteration */int pos;int found = 0;if (!list) return -1;/* at must >= 0 */if (at < 0) return -1;/* start from the first element */node = list->next;pos = 0;while (NULL != node) {if (at == pos) { found = 1; /* found the position */node->data = x;break; }/* move to the next */node = node->next;pos++;}if (1 == found) {return 0;} else {return -1;}
}int InsertLinklist(linklist_t list, int at, data_t x)
{/* * node_at and pos_at are used to locate the position of node_at.* node_prev follows the node_at and always points to previous node * of node_at.* node_new is used to point to the new node to be inserted.*/linknode_t *node_prev, *node_at, *node_new;int pos_at;int found = 0;if (NULL == list) return -1;/* at must >= 0 */if (at < 0) return -1;node_new = malloc(sizeof(linknode_t));if (NULL == node_new) {return -1;}node_new->data = x; /* assigned value */node_new->next = NULL;node_prev = list;node_at = list->next;pos_at = 0;while (NULL != node_at) {if (pos_at == at) {/* * found the node 'at'*/ found = 1;break; }/* move to the next pos_at */node_prev = node_at;node_at = node_at->next;pos_at++;}if (found) {/* insert */node_new->next = node_at;node_prev->next = node_new;} else {/* * If not found, means the provided "at"* exceeds the upper limit of the list, just * append the new node to the end of the list.*/node_prev->next = node_new;}return 0;
}int DeleteLinklist(linklist_t list, int at)
{/* * node_at and pos_at are used to locate the position of node_at.* node_prev follows the node_at and always points to previous node * of node_at.*/linknode_t *node_prev, *node_at;int pos_at;int found = 0;if (!list) return -1;/* at must >= 0 */if (at < 0) return -1;node_prev = list;node_at = list->next;pos_at = 0; while (NULL != node_at) {if (pos_at == at) {/* * found the node 'at'*/ found = 1;break; }/* move to the next pos_at */node_prev = node_at;node_at = node_at->next;pos_at++;}if (found) {/* remove */node_prev->next = node_at->next;free(node_at);return 0;} else {return -1;}
}linklist_t ReverseLinklist(linklist_t list)
{linknode_t *node; /* iterator */linknode_t *node_prev; /* previous node of iterator */linknode_t *node_next; /* next node of iterator, * used to backup next of iterator */if (NULL == list) return NULL;node_prev = NULL;node = list->next;while (NULL != node) {/** step1: backup node->next* due to the next of iterator will be* modified in step2*/node_next = node->next;/* * when iterator reaches the last node * of original list, make the list head* point to the last node, so the original* last one becomes the first one.*/if (NULL == node_next) {list->next = node;}/* * step2: reverse the linkage between nodes* make the node pointer to the previous node,* not the next node*/ node->next = node_prev; /* * step3: move forward */node_prev = node;node = node_next;}return list;
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"int main()
{int i;data_t x;linklist_t p;p = CreateEmptyLinklist();data_t a[10] = {1,3,5,7,9,11,13,15,17,19};for(i = 0;i < 10;i++){InsertLinklist(p,i,a[i]);}ReverseLinklist(p);printf("The length of the list is:%d\n",LengthLinklist(p));GetLinklist(p,4,&x);printf("The NO.4 of this list is:%d\n",x);SetLinklist(p,4,100);GetLinklist(p,4,&x);printf("After updating!The No.4 0f this list is:%d\n",x);DeleteLinklist(p,4);printf("After updating!The length of the list is:%d\n",LengthLinklist(p));GetLinklist(p,4,&x);printf("After updating!The No.4 0f this list is:%d\n",x);ReverseLinklist(p);ClearLinklist(p);if(EmptyLinklist(p))printf("This list is empty!\n");DestroyLinklist(p);printf("This list is destroyed!\n");return 0;
}
Makefile
.PHONY : clean
OBJ = test
OBJS = linklist.o main.o
CFLAGS = -c -g -Wall
CC = gcc
$(OBJ):$(OBJS) @$(CC) -o test -g $(OBJS)
main.o:main.c linklist.h @$(CC) $(CFLAGS) main.c
linklist.o:linklist.c linklist.h @$(CC) $(CFLAGS) linklist.c
clean: @rm -rf *.o
执行结果如下:
2.2.2循环链表
前面我们学习了单向链表,现在介绍单向循环链表,单向循环链表是单链表的一种改进,若将单链表的首尾节点相连,便构成单向循环链表结构,如下图:
对于一个循环链表来说,其首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,可以选择开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。再来看另一种方法,循环链表可以被视为“无头无尾”。这种列表很利于节约数据存储缓存, 假定你在一个列表中有一个对象并且希望所有其他对象迭代在一个非特殊的排列下。指向整个列表的指针可以被称作访问指针。
循环链表中第一个节点之前就是最后一个节点,反之亦然。循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。对于新加入的节点应该是在第一个节点之前还是最后一个节点之后可以根据实际要求灵活处理,区别不大。当然,如果只会在最后插入数据(或者只会在之前),处理也是很容易的。
2.2.2.1 Joseph问题(约瑟夫环)
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人找到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
约瑟夫环用数学问题来描述就是:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。如何用循环链表来求解Josephu问题?
下面我们用单向循环链表来模拟这个问题:
#include <stdio.h>
#include <stdlib.h> typedef int data_t; typedef struct node_t
{ data_t data; struct node_t *next;
}linknode_t,*linklist; linklist CreateList(int n)
{ int i; linklist p,head,tail; head = NULL; for(i = 1;i <= n;i++) { p = (linklist)malloc(sizeof(linklist)); if(p == NULL) { printf("malloc fails!\n"); } p->data = i; if(head == NULL) { head = p; tail = head; } else { tail->next = p; } tail = p; } tail->next = head; return head;
}
void Joseph(int n,int k,int m)
{ int i; linklist p,r; p = CreateList(n); for(i = 1;i < k;i++) //从第K个人开始数 { p = p->next; } while(p->next != p) { for(i = 1;i <= m-2;i++) //数到第m个人,去自杀 p = p->next; r = p->next; p->next = r->next; printf("%d->",r->data); free(r); p = p->next;//从下一个人继续数 } printf("%d\n",p->data);
}
int main()
{ Joseph(41,1,3); return 0;
}
输出结果如下:
3->6->9->12->15->18->21->24->27->30->33->36->39->1->5->10->14->19->23->28->32->37->41->7->13->20->26->34->40->8->17->29->38->11->25->2->22->4->35->16->31
我们可以看到,最后两个是16和31,这样,约瑟夫和他的朋友就躲过了一劫!
2.2.2.2判断一个链表是不是循环链表(如何判定这个链表当中是否包含有环路)
解决方法:
判断是否是循环链表时,也设置两个指针,慢指针和快指针,让快指针比慢指针每次移动快两次。如果快指针追赶上慢指针,则为循环链表,否则不是循环链表,如果快指针或者慢指针指向NULL,则不是循环链表。
代码如下:
#include <stdio.h>
#include <stdlib.h> typedef int data_t; typedef struct node_t
{ data_t data; struct node_t *next;
}linknode_t,*linklist;
linklist CreateList(int n)
{ int i; linklist p,head,tail; head = NULL; for(i = 1;i <= n;i++) { p = (linklist)malloc(sizeof(linklist)); if(p == NULL) { printf("malloc fails!\n"); } p->data = i; if(head == NULL) { head = p; tail = head; } else { tail->next = p; } tail = p; } tail->next = head; return head;
}
int JudgeIsloop(linklist list)
{ int flag = 0; linknode_t *slow,*fast; if(list == NULL) return 0; slow = list; fast = list->next; while(slow) { if(fast == NULL || fast->next == NULL)//走到头了 return 0; else if(fast == slow || fast->next == slow)//二者相遇,因为fast走的快,如果fast->next指向slow,也是循环的 { flag = 1; return 1; } else { slow = slow->next;//慢指针走一步 fast = fast->next->next;//快指针走两步 } } return 0;
}
int main()
{ int i; int flag = 0; linklist list; list = CreateList(10); JudgeIsloop(list); if(flag = 0) printf("The list is not a looplist!\n"); else { printf("The list is a looplist!\n");//循环链表则打印出来 for(i = 0;i < 10;i++) { printf("%d->",list->data); list = list->next; } printf("%d\n",list->data); } return 0;
}
结果如下:
The list is a looplist!
1->2->3->4->5->6->7->8->9->10->1
这篇关于《数据结构》第2章 表(C语言描述)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!