思路:对链表的编写采用自定义头文件,用【#ifndef #define #endif】这组定义头文件,其次,我们约定头结点没有数据(约定大于技术)。
头文件引用:<>指定根目录 " "当前目录或指定目录
头文件中的 #ifndef/#define/#endif 防止该头文件被重复引用
其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。
预处理阶段
比如:存在a.h文件
#include "b.h"而此时b.cpp文件导入了
#include "a.h" 和#include "b.h"此时就会造成c.b重复引用。
下面给一个#ifndef/#define/#endif的格式:
#ifndef A_H意思是"if not define a.h" 如果不存在a.h
接着的语句应该#define A_H 就引入a.h
最后一句应该写#endif 否则不需要引入
root@ubuntu:/mnt/U_share/DATA/linklist# mkdir inc root@ubuntu:/mnt/U_share/DATA/linklist# mkdir src root@ubuntu:/mnt/U_share/DATA/linklist# vim inc/linklist.h root@ubuntu:/mnt/U_share/DATA/linklist# vim src/linklist.c
单链表:_ LINKLIST_H_:
#ifndef _LINKLIST_H_ #define _LINKLIST_H_ #include <stdio.h> #include <stdlib.h> typedef int type; typedef struct node{ type data; struct node *pnext; //pLinklist pnext;是错的,机器思想(一步一步) }Linklist,*pLinklist; //初始化链表 int list_init(pLinklist *L); //链表判空 int list_empty(pLinklist L); //链表头插法 int list_insert_head(pLinklist L,type data); //删除指定值所在结点 int list_delete_data(pLinklist L,type data); //查询链表元素 pLinklist list_select_data(pLinklist L,type data); //链表元素展现 int list_show(pLinklist L); //释放链表 int list_destory(pLinklist *L); //递归展现 int list_show_back(pLinklist l); #endif
单链表:Linklist.c:
#include "linklist.h" //初始化链表 int list_init(pLinklist *L) { *L = malloc(sizeof(Linklist)); if(NULL == *L){ perror("L_malloc"); return -1; } (*L)->pnext = NULL; return 0; } //链表判空 int list_empty(pLinklist l) { if(NULL==l) return -1; if(NULL == l->pnext) return 0; return -1; } //链表头插法 int list_insert_head(pLinklist l,type data) { if(NULL==l) return -1; //创建新的结点存储数据 pLinklist p = malloc(sizeof(Linklist)); if(NULL==p){ perror("data_malloc"); return -1; } p->data = data; //新结点的pnext指向原来头结点的下一个结点 p->pnext = l->pnext; //头结点的pnext指向插入的结点 l->pnext = p; return 0; } //删除指定值所在结点 int list_delete_data(pLinklist l,type data) { if(NULL==l || 0==list_empty(l)) return -1; //标记要删除的结点 pLinklist p; while(l->pnext){//隐含l->pnext != NULL if(l->pnext->data == data){ p = l->pnext; //待删除结点p的前一个结点指向其后一个结点 l->pnext = l->pnext->pnext; free(p); } l = l->pnext;//结点后移 } return 0; } //查询链表元素 pLinklist list_select_data(pLinklist l,type data) { if(NULL==l || 0==list_empty(l)) return NULL; while(l->pnext){ //约定头结点不存放数据 if(data == l->pnext->data) return l->pnext; l = l->pnext; } return NULL; } //链表元素展现 int list_show(pLinklist l) { if(NULL==l || 0==list_empty(l)) return -1; while(l->pnext){ printf("%d ",l->pnext->data); l = l->pnext; } puts(""); return 0; } //释放链表 int list_destory(pLinklist *L) { if(NULL==*L) return -1; #if 0 //标记头结点,从头结点开始释放 pLinklist p; while(*L){ p = *L; *L = (*L)->pnext;//头结点后移 free(p);//释放原来的头结点 }//循环结束后其值为NULL #else //先删除数据结点,再删除头结点 pLinklist p = (*L)->pnext,q; while(p){ q = p; p = p->pnext; free(q); } free(*L); *L = NULL; return 0; } #endif //递归 int list_show_back(pLinklist l) { if(NULL==l) return -1; list_show_back(l->pnext); printf("%d ",l->data); }
单链表:main.c
#include "linklist.h" int list_init(pLinklist *L); int list_empty(pLinklist l); int list_insert_head(pLinklist l, type data); int list_delete_data(pLinklist l, type data); pLinklist list_select_data(pLinklist l,type data); int list_show(pLinklist l); int list_destory(pLinklist *L); int list_show_back(pLinklist l); int main(int argc, char *argv[]) { pLinklist p = NULL; type data; list_init(&p); while(1){ printf("please input data:"); if(0==scanf("%d",&data)){ getchar(); break; } list_insert_head(p,data); } list_show(p); while(1){ printf("please delete data:"); if(0==scanf("%d",&data)){ getchar(); break; } list_delete_data(p,data); } //递归show是反过来的 list_show_back(p); puts(""); list_show(p); fputs("please show data:",stdout); scanf("%d",&data); printf("%p",list_select_data(p,data)); list_destory(&p); if(NULL == p){ fputs("\nthis is NULL!\n",stdout); } return 0; }
makefile:
all: gcc ./src/*.c -o link -I ./inc/ clean: rm link
单链表结果:
root@ubuntu:/mnt/U_share/DATA/linklist# make gcc ./src/*.c -o link -I ./inc/ root@ubuntu:/mnt/U_share/DATA/linklist# ls inc link makefile src root@ubuntu:/mnt/U_share/DATA/linklist# ./link please input data:1 please input data:1 please input data:2 please input data:3 please input data:3 please input data:3 please input data:q 3 3 3 2 1 1 please delete data:1 please delete data:q 1 2 3 3 3 0 //这里最后是一个0,是因为第一个头结点是NULL,没存数据(约定) 3 3 3 2 1 please show data:2 0x558b4eb7bb20 //返回的是2的地址 this is NULL!
====================================================================
总结:编程少写跳转,goto,break,continue,极度灵活,一旦掌握不好就会出现bug,另外,机器会预取址(下两个),影响性能
返回首地址要记录,strcpy();
删除值,要考虑尾节点和连续值
链表,指定位置插入(是否合法,>0或者<max,可能段错误)
空指针无法被访问
结构体记录,可以没必要,易出bug,全局变量(改),影响性能,
单独定义一个结构体,只记录另一个结构体的头节点
用返回值或者指针编写函数,返回值弊端是返回的值为-1
可返回局部变量的地址,不可返回局部变量,即改形参对实参无影响
多个写递归,找到结点后将该结点的地址传进去,继续递归
链表是离散的,不能当作顺序表来处理,不能++或者--,需要定义指针进行指向释放链表有两种方法①直接从头结点释放②先释放数据,在释放结点
-I 链接指定头文件
段错误:非法访问内存(本质)
递归是存放进栈区的,,不好把控,时间复杂度也高,少用递归,进程结束后自动释放内存
程序结束会自动释放内存,那么为什么要释放,即destory()呢?这是为了程序很大时,内存泄漏可能导致内存不够。
循环链表:(待改进)
头文件:_LOOPLIST_H_
#ifndef _LOOPLIST_H_ #define _LOOPLIST_H_ #include <stdio.h> #include <stdlib.h> typedef int type; typedef struct node{ type data; struct node *next; }Looplist,*pLooplist; //循环链表初始化 int list_init(pLooplist *L); //循环链表判空 int list_empty(pLooplist l); //循环链表增加结点、尾插 int list_insert_head(pLooplist l,type data); //循环链表删除指定值所在节点 int list_delete_data(pLooplist l,type data); //循环链表查询 Plooplist list_select_data(pLooplist l, type data); //循环链表show int list_show(pLooplist l); //循环链表destory int list_free(pLooplist *L); //循环链表递归show int list_show_back(pLooplist phead, pLooplist l); #endif
外部函数文件:Looplist.c
#include "clooplist.h" //循环链表初始化 int list_init(pLooplist *L) { *L = malloc(sizeof(Looplist)); if(NULL == *L){ perror("init_malloc"); return-1; } (*L)->next = *L; return 0; } //循环链表判空 int list_empty(pLooplist l) { if(NULL == l) return -1; if(l->next == l) return 0; return -1; } //循环链表增加结点、尾插 int list_insert_tail(pLooplist l,type data) { if(NULL == l) return -1; //头结点定义为 l pLooplist phead = l; //因为是尾插,所以要先遍历到末尾 while(l->next != phead){ l = l->next; } pLooplist p = malloc(sizeof(Looplist)); if(NULL == p){ perror("data_malloc"); return -1; } p->data = data; //这种顺序相对而言更好点,先指再断_指,可思考互换 p->next = phead; l->next = p; return 0; } //循环链表删除指定值所在节点 int list_delete_data(pLooplist l,type data) { if(NULL==l || 0==list_empty(l)){ perror("s"); return -1; } pLooplist phead = l,p = NULL; while(l->next != l){ if(l->next->data == data){ p = l->next; l->next = p->next; free(p); //暂停此循环是为了删除连续的相同结点 continue; } l = l->next; } return 0; } //循环链表查询 pLooplist list_select_data(pLooplist l, type data) { if(NULL==l || 0==list_empty(l)) return NULL; pLooplist phead = l; while(l->next != phead){ if(data == l->next->data) return l->next; l = l->next; } return NULL; } //循环链表show int list_show(pLooplist l) { if(NULL == l || 0==list_empty(l)) return -1; pLooplist phead = l; while(l->next != phead){ printf("%d ",l->next->data); l = l->next; } return 0; } //循环链表destory int list_destory(pLooplist *L) { if(NULL == *L) return -1; pLooplist phead = *L,p; //先释放数据结点,再释放头结点 phead = phead->next; while(phead != *L){ p = phead; phead = phead->next; free(p); }//和上列循环类似,只是写法不一样 free(*L); *L = NULL; return 0; } //循环链表递归show int list_show_back(pLooplist phead, pLooplist l) { if(phead == l)//前提是phead = l(已经) return 0; list_show_back(phead,l->next); printf("%d ",l->data); }
主函数:main.c
#include "clooplist.h" int list_init(pLooplist *L); int list_empty(pLooplist l); int list_insert_tail(pLooplist l,type data); int list_delete_data(pLooplist l,type data); pLooplist list_select_data(pLooplist l, type data); int list_show(pLooplist l); int list_destory(pLooplist *L); int list_show_back(pLooplist phead, pLooplist l); int main(int argc, char *argv[]) { pLooplist p = NULL; list_init(&p);//初始化 type data; while(1){ printf("please input data : "); if(0 == scanf("%d",&data)){ getchar(); break; } list_insert_tail(p,data);//尾插 } list_show_back(p,p->next);//递归show puts(""); list_show(p);//show puts(""); type i; while(1){ printf("please delete data : "); if(0 == scanf("%d",&i)){ getchar(); break; } list_delete_data(p,i);//删除 } list_show(p); printf("please input select:"); scanf("%d",&data); printf("the select:%p\n",list_select_data(p,data)); list_destory(&p); if(NULL == p) fputs("this is NULL\n",stdout); return 0; }
结果:
please input data : 1 please input data : 2 please input data : 3 please input data : 3 please input data : 4 please input data : q 4 3 3 2 1 1 2 3 3 4 please delete data : 2
总结:
scanf(),返回值个数
尝试删除尾节点,NULL已经被赋,尾结点要分类讨论
3 2 1 1 1 删1 前移+后移,continue
一个主函数中两个while(1)?
双向循环链表:
头文件:_DLOOPLIST_H_
#ifndef _DLOOPLIST_H_ #define _DLOOPLIST_H_ #include <stdio.h> #include <stdlib.h> //C 语言标准(C89) 没有定义布尔类型,该头文件宏定义包含 true 和 false #include <stdbool.h> typedef int type; typedef struct node{ type data;//自定义型的数据 struct node *pb, *pn;//前、后指针 }Dlooplist, *pDlooplist; //返回值初始化双向循环链表 pDlooplist list_create(); //返回值为布尔类型的双向循环链表判空 bool list_empty(pDlooplist l); //双向循环链表尾插数据函数 bool list_insert_tail(pDlooplist l, type data); //删除函数 bool list_delete_data(pDlooplist l, type data); //与指定值相等的节点全部修改 bool list_change(pDlooplist l, type val, type data); //返回地址的双向链表查询函数 //第三参数是二级指针,负责返回查询到节点的地址 bool list_select_data(pDlooplist l, type data, pDlooplist *Node); //正序show bool list_show_seq(pDlooplist l); //逆序show bool list_show_back(pDlooplist l); //释放函数 bool list_free(pDlooplist *L); #endif
外部函数文件:dlooplist.c
#include "dlooplist.h" //返回值初始化双向循环链表 pDlooplist list_create() { pDlooplist p = malloc(sizeof(Dlooplist)); if(NULL == p){ perror("list_malloc"); return NULL; } //前、后结点都指向自己 p->pb = p->pn = p; return p; } //返回值为布尔类型的双向循环链表判空 bool list_empty(pDlooplist l) { if(NULL == l) return false; if((l==l->pb) && (l==l->pn)) return true; return false; } //双向循环链表尾插数据函数 bool list_insert_tail(pDlooplist l, type data) { if(NULL == l) return false; pDlooplist p = malloc(sizeof(Dlooplist)); if(NULL == p){ perror("data_malloc"); return false; } p->data = data; p->pb = l->pb;//p->pb = l->pn; p->pn = l;//p->pn = l->pb; //让原来的尾结点的 pn 指向新的尾结点 l->pb->pn = p;//l->pn = p; //让头结点的 pb 指向新的尾结点 l->pb = p;//l->pb = p; return true; } //逆序删除函数 bool list_delete_data(pDlooplist l, type data) { if(NULL==l || list_empty(l)) return false;//隐含list_empty(l)==true pDlooplist phead = l,p; while(l->pb != phead){//or l->pn != phead if(data == l->pb->data){//判断相应改变 p = l->pb; l->pb = l->pb->pb; p->pb->pn = l; free(p); continue; } l = l->pb; } return true; } //与指定值相等的节点全部修改,val:结果 data:目标 bool list_change(pDlooplist l, type val, type data) { if(NULL == l || list_empty(l)) return false; pDlooplist phead = l; while(l->pn != phead){ if(data == l->pn->data) l->pn->data = val; l = l->pn; } return true; } //返回地址的双向链表查询函数 //第三参数是二级指针,负责返回查询到节点的地址 bool list_select_data(pDlooplist l, type data, pDlooplist *Node) { if(NULL == l || list_empty(l)){ *Node = NULL; return false; } pDlooplist phead = l; while(l->pn != phead){ if(data == l->pn->data){ *Node = l->pn; return true; } l = l->pn; } *Node = NULL; return true; } //正序show bool list_show_seq(pDlooplist l) { if(NULL==l || list_empty(l)) return -1; pDlooplist phead = l; while(l->pn != phead){ printf("%d ",l->pn->data); l = l->pn; } puts(""); return true; } //逆序show bool list_show_back(pDlooplist l) { if(NULL==l || list_empty(l)) return -1; pDlooplist phead = l; while(l->pb != phead){ printf("%d ",l->pb->data); l = l->pb; } puts(""); return true; } //释放函数 bool list_destory(pDlooplist *L) { if(NULL == *L) return false; pDlooplist p = (*L)->pn; while(p != *L){ p = p->pn; free(p->pb); } free(*L); *L = NULL; return true; }
主函数: main.c
#include "dlooplist.h" pDlooplist list_create(); bool list_empty(pDlooplist l); bool list_insert_tail(pDlooplist l, type data); bool list_delete_data(pDlooplist l, type data); bool list_change(pDlooplist l,type val,type data); bool list_select_data(pDlooplist l,type data,pDlooplist *Node); bool list_show_seq(pDlooplist l); bool list_show_back(pDlooplist l); bool list_destory(pDlooplist *L); int main(int argc, char *argv[]) { pDlooplist p = list_create(),q=NULL; if(NULL == p)//指针 p 未定义成功 return -1; type data; while(1){ printf("please input data : "); if(0 == scanf("%d",&data)){ getchar(); break; } list_insert_tail(p,data);//尾插 } list_show_back(p);//逆序show list_show_seq(p);//正序show while(1){ printf("please delete data : "); if(0 == scanf("%d",&data)){ getchar(); break; } list_delete_data(p,data);//删除 } list_show_seq(p); printf("please input select:"); scanf("%d",&data); printf("the select:%p\n",list_select_data(p,data,&q)); //将值 3 全部改为 2 list_change(p,2,3); list_show_seq(p); list_destory(&p); if(NULL == p) fputs("this is NULL\n",stdout); return 0; }
结果:
root@ubuntu:/mnt/U_share/DATA/linklist/linklist_duplexing# ./link please input data : 1 please input data : 1 please input data : 2 please input data : 2 please input data : 3 please input data : 3 please input data : 4 please input data : 5 please input data : 6 please input data : q 6 5 4 3 3 2 2 1 1 1 1 2 2 3 3 4 5 6 please delete data : 1 please delete data : q 2 2 3 3 4 5 6 please input select:4 the select:0x1 2 2 2 2 4 5 6 this is NULL
总结:指针 = 结点
双向链表:更改四个指针,至少四行代码
根据需求而定,编写代码时要时刻注意指针的指向,画图更好理解
*node = null;防止成为野指针链表:一种物理存储单元上非连续、非顺序的存储结构 , 数据元素的逻辑顺序是通过链表中的 指针 链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:存储 数据元素的数据域,存储下一个结点地址的指针域。
相比于线性表顺序结构而言,操作复杂,但由于不用必须按顺序存储,链表在插入的时候可以达到O (1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O (n)的时间,而线性表和顺序表相应的时间复杂度分别是O (logn)和O (1)。
优点:使用链表结构可以克服 数组 链表需要预先知道数据大小的缺点,也可充分利用计算机内存空间,实现灵活的内存动态管理。
====================================================================
顺序存储的特点:
逻辑顺序与物理顺序一致,本质上是用内存中一片连续的空间构建成数组进行存储;存储密度大,存储空间利用率相对而言更高
链式存储的特点:
灵活,时间复杂度较小,用元素结点的指向进行相互关联,进而构成一条链表,其插入、删除结点不需要移动其余节点,只改变指向即可
静态存储的特点:
固定内存,在程序运行的过程中不考虑动态分配以及追加内存的问题
动态存储的特点:
动态分配内存,也可动态增加内存,能有效的利用内存资源,使程序具有良好的延展性