数据结构之链表(c语言版)
链表是线性表,链表的特点就是可以动态增减元素。种类有单向链表、双向链表,循环链表。
一、单链表
单链表的储存思想使用指针表示节点之间的逻辑关系,它的储存单元可以连续也可以不连续,每个储存单元需要储存信息和储存与后继节点的地址信息,储存单元又称之为节点。单链表由头指针唯一确定,整个单链表的操作必须由头指针开始。
链表的单位是一个一个节点,每个节点分为数据域和指针域,数据域存放数据,指针域存放指向下一个节点的指针(没有指针的语言存放的是对下一个节点的引用)。
头节点通常不放数据(也可以存放数据),尾节点指针域为空(循环链表不为空)。
单链表示意图:
c语言实现单链表1.0
还是1.0版本和2.0版本哈,2.0是后来增加的,更加健壮!
#include
#include
//定义节点
typedef struct list
{ int data;
struct list *next;
/* data */
}Node;
//节点初始化
Node *create_node(){
Node *p=(Node *)malloc(sizeof(Node));
//节点
p->next=NULL;
return p;
}
//建立链表,尾部增加
Node *link_list(int d[],int size){
Node *head=create_node();
Node *tail=head; //尾节点,此时尾节点就是头节点
for(int i=0;i Node *p=(Node *)malloc(sizeof(Node)); p->data=d[i]; p->next=NULL; tail->next=p; tail=p;//p变为尾节点 } return head; } //追加 int append(Node *head,int e){ Node *p=create_node(); p->data=e; Node *s=head; while(s->next!=NULL){ s=s->next; } s->next=p; return 1; } //长度 int Len(Node *head){ Node *p=head; int count=0; for(;p!=NULL;count++){ p=p->next; } return count; } //插入 int Insert(Node *head,int i,int e){ if(i<1){ printf("插入错误,程序终止"); exit(0); } if(i>=Len(head)+1){ //此时为追加 append(head,e); return 1; } int n=1; Node *p=head; for(;n
p=p->next; } Node *s=create_node(); s->data=e; s->next=p->next; p->next=s; return 1; } //删除 int Delete(Node *head ,int i){ if(i<1||i>Len(head)){ printf("删除非法,程序终止"); exit(0); } Node *f=head;//前指针 Node *b;//后指针 for(int n=1;n
f=f->next; b=(f->next)->next; } Node *s=f->next;//此为被销毁的节点 f->next=b; free(s);//释放内存 return 1; } //查找 int Query(Node *head ,int i){ if(i<1||i>Len(head)){ printf("查找非法,程序终止"); exit(0); } Node *p=head;//工作指针 for(int n=1;n
p=p->next; } int e=p->data;//查询的元素 return e; } //修改 int Update(Node *head ,int i,int e){ if(i<1||i>Len(head)){ printf("修改非法,程序终止"); exit(0); } Node *p=head;//工作指针 for(int n=1;n
p=p->next; } p->data=e; return 1; } //判空 int is_Empty(Node *head){ if(head->next==NULL){ return 1; } return 0; } //遍历链表 void display(Node *head){ Node *p=head->next;//头节点没有数据 printf("此时有%d个节点\n",Len(head)); for(;p!=NULL;p=p->next){ printf("The data is %d\n",p->data); } } //销毁链表 void Destory(Node *head){ for(Node *p=head;head!=NULL;){ head=head->next; free(p); p=head; } printf("链表已经销毁\n"); exit(0); } 这里把大部分功能都用函数表达了,有些功能用属性似乎更加简便,不过无伤大雅。 在主函数中测试一下功能: int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(p); append(p,18); display(p); Insert(p,2,37); Insert(p,4,23); Insert(p,14,37); display(p); Delete(p,2); display(p); int i=9; int e=Query(p,i); printf("查询的是第%d个元素,值为%d\n",i,e); Update(p,i,10); printf("修改了%d号元素,现在值为%d\n",i,Query(p,i)); Destory(p); } 测试结果: 创建链表 此时有6个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 此时有7个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 The data is 18 此时有10个节点 The data is 2 The data is 37 The data is 1 The data is 23 The data is 5 The data is 11 The data is 6 The data is 18 The data is 37 此时有9个节点 The data is 2 The data is 1 The data is 23 The data is 5 The data is 11 The data is 6 The data is 18 The data is 37 查询的是第9个元素,值为37 修改了9号元素,现在值为10 链表已经销毁 c语言实现单链表2.0 从全局静态变量来表示头节点和节点数量,此时代码逻辑简单多了。 增删改查功能都没有实现,可以自行摸索。 #include #include //定义节点 typedef struct list { int data; struct list *next; /* data */ }Node; static int count=0;//节点数量,代替了Len函数中的逻辑 static Node *head=NULL;//头节点 //节点初始化 Node *create_node(){ Node *p=(Node *)malloc(sizeof(Node)); //节点 p->next=NULL; return p; } //建立链表,尾部增加 Node *link_list(int d[],int size){ head=create_node(); Node *tail=head; //尾节点,此时尾节点就是头节点 for(int i=0;i Node *p=create_node(); p->data=d[i]; p->next=NULL; tail->next=p; tail=p;//p变为尾节点 printf("链表增加元素%d\n",d[i]); } count=size+1;//头节点也算 printf("链表创建成功\n"); return head; } //长度 int Len(){ //原先的代码去掉,直接由count代替 return count; } //判空 int is_Empty(){ if(count==0){ return 1; } return 0; } //遍历链表 void display(){ printf("此时有%d个节点\n",count); Node *p=head->next; // for(;p!=NULL;p=p->next){ // printf("The data is %d\n",p->data); // } for(int i=1;i printf("The data is %d\n",p->data); p=p->next; } } int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(); } 二、单向循环链表 循环链表有单向的也有双向的,其实就是尾节点的指针域指向了头节点,成为循环结构。这样从任意位置都可以遍历所有。 在单链表1.0的基础上稍作修改,一个单向循环链表就完成了。在指针的指向方面稍作修改就可以完成,部分方法就没有提供了,可以自行摸索。 单循环链表1.0 #include #include //定义节点 typedef struct list { int data; struct list *next; /* data */ }Node; //节点初始化 Node *create_node(){ Node *p=(Node *)malloc(sizeof(Node)); //节点 p->next=NULL; return p; } //建立链表,尾部增加 Node *link_list(int d[],int size){ Node *head=create_node(); Node *tail=head; //尾节点,此时尾节点就是头节点 for(int i=0;i Node *p=(Node *)malloc(sizeof(Node)); p->data=d[i]; p->next=head;//指向头节点 tail->next=p; tail=p;//p变为尾节点 } return head; } //长度 int Len(Node *head){ Node *p=head; if(p==NULL){ return 0; } p=head->next; Node *s=head; int count=1; for(;p!=s&&p!=NULL;count++){ p=p->next; } return count; } //判空 int is_Empty(Node *head){ if(head->next==NULL){ return 1; } return 0; } //遍历链表 void display(Node *head){ Node *p=head->next;//头节点没有数据 Node *s=head;//标记指针 printf("此时有%d个节点\n",Len(head)); for(;p!=s;p=p->next){ printf("The data is %d\n",p->data); } } 在主函数中测试一下: int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(p);//遍历循环链表 Node * s=(p->next)->next;//指向其他节点的指针 display(s);//其他节点遍历 } 结果如下,可以看到不是预期的结果,某个数据。。。是1995024,这是个啥,这是头节点的data,自动分配的。 创建链表 此时有6个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 此时有6个节点 The data is 5 The data is 11 The data is 6 The data is 1995024 The data is 2 这肯定不行啊,说好的头节点不放数据的,所以我们遇到头节点得跳到下一级。1.0版本的可以自己实现一下! 我们搞个2.0版本的。 单循环链表2.0 #include #include //定义节点 typedef struct list { int data; struct list *next; /* data */ }Node; static Node *head=NULL; static int count=0; //节点初始化 Node *create_node(){ Node *p=(Node *)malloc(sizeof(Node)); //节点 p->next=NULL; return p; } //建立链表,尾部增加 Node *link_list(int d[],int size){ head=create_node(); Node *tail=head; //尾节点,此时尾节点就是头节点 for(int i=0;i Node *p=create_node(); p->data=d[i]; p->next=head;//指向头节点 tail->next=p; tail=p;//p变为尾节点 } count+=size+1;//节点数量 return head; } //查询 int query(int i){ is_error(i); Node *p=head; for(int n=1;n
p=p->next; } return p->data; } //是否非法 int is_error(int i){ if(is_Empty()){ printf("NULL,非法\n"); return 1; } if(i<1||i>count){ printf("查询超长,非法\n"); return 2; } return 0; } //长度 int Len(){ return count; } //判空 int is_Empty(){ if(count==0){ return 1; } return 0; } //遍历链表 void display(Node *p){ printf("此时有%d个节点\n",count); for(int i=1;i if(p==head){ //判断是否是头指针 p=p->next; } printf("The data is %d\n",p->data); p=p->next; } } 主函数中测试一下: int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(p);//遍历循环链表 p=p->next->next;//偏移 display(p);//其他节点遍历 } 创建链表 此时有6个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 此时有6个节点 The data is 1 The data is 5 The data is 11 The data is 6 The data is 2 此时结果应该是正确的! 三、双向链表 双向链表即有两个指向的链表,通常指指向前节点和后继节点的链表。这样也是从任何部位都能遍历全部元素, 且查找元素时,可以向前查找或者向后查找,不必完全遍历全链表。 双向链表示意图: c语言实现双链表 在单链表2.0的基础上研究双链表。 先创建一个双向链表。 #include #include //定义节点 typedef struct list { int data; struct list *next;//双指针 struct list *prev; /* data */ }Node; static int count=0;//节点数量 static Node *head=NULL;//头节点 //节点初始化 Node *create_node(){ Node *p=(Node *)malloc(sizeof(Node)); //节点 p->next=NULL; p->prev=NULL; return p; } //尾节点 Node *get_tail(){ Node *tail=head; for(int i=1;i tail=tail->next; } return tail; } //建立链表,尾部增加 Node *link_list(int d[],int size){ head=create_node();//头节点初始化 Node *tail=head; //尾节点,此时尾节点就是头节点 for(int i=0;i Node *p=create_node(); p->data=d[i]; tail->next=p; p->prev=tail; tail=p;//p变为尾节点 } count=size+1; return head; //返回头节点 } //长度 int Len(){ return count; } //判空 int is_Empty(){ if(count==0){ return 1; } return 0; } //遍历链表,从头节点开始 void display(){ printf("此时有%d个节点\n",count); Node *p=head->next; for(;p!=NULL;p=p->next){ printf("The data is %d\n",p->data); } } //查找元素 //查找元素 Node* query(int i){ if(error(i)){ return NULL; } Node *p; if(i<=(int)(count/2)){//从头结点开始查找 p=head; for(int n=1;n
p=p->next; } return p; }else //从尾节点向前查找 { p=get_tail(); for(int n=0;n p=p->prev; } return p; } return NULL; } //得到节点 Node *get_node(int i){ Node *p=head; if(i==count){ return get_tail(); } if(error(i)){ return NULL; } for(int s=1;s
p=p->next; } return p; } //判断传入参数是否非法 int error(int i){ if(is_Empty()){ printf("链表NULL,非法\n"); return 1; } if(i<1||i>count){ printf("查询超长,非法\n"); return 2; } return 0; } 测试一下: int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(); printf("查询的元素是%d\n",query(5)->data); printf("查询的元素是%d\n",query(2)->data); } 此时可以查询。 创建链表 此时有6个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 查询的元素是11 查询的元素是2 再加入删除、更新、插入函数。 //插入 void insert(int i,int e){ Node *s =query(i); Node *p=create_node(); p->data=e; p->next=s; p->prev=s->prev; (s->prev)->next=p; s->prev=p; count++; } //删除 void delete(int i){ Node *s=query(i); Node *p,*n; //修改指针指向 p=s->prev; n=s->next; p->next=n; n->prev=p; free(s); //销毁节点 count--;//数量减少 } //更新 void update(int i,int e){ Node *p=query(i); p->data=e; } 再次测试: int main(){ int a[5]={2,1,5,11,6}; int size=sizeof(a)/sizeof(int); printf("创建链表\n "); Node *p=link_list(a,size); display(); printf("查询的元素是%d\n",query(5)->data); printf("查询的元素是%d\n",query(2)->data); update(2,8); display(); delete(2); display(); insert(2,8); display(); } 如下所示: 创建链表 此时有6个节点 The data is 2 The data is 1 The data is 5 The data is 11 The data is 6 查询的元素是11 查询的元素是2 此时有6个节点 The data is 8 The data is 1 The data is 5 The data is 11 The data is 6 此时有5个节点 The data is 1 The data is 5 The data is 11 The data is 6 此时有6个节点 The data is 8 The data is 1 The data is 5 The data is 11 The data is 6 四、双向循环链表 双向循环链表的用处比较广泛、不过就是双向链表的基础上进行了循环。构造难度不大,可以参考单链表循环实现。 数据结构与算法学习目录