最近学算法,做一个简单记录。把容易出错和混淆的地方记录一下。
这里编程语言选择为C。
什么是链表?
要搞清楚链表,首先要知道顺序表。顺序表也是我们常说的数组,数组有个最大的特点,它的存储空间在内存上是连续的。数组解决了我们编程中许多问题,如果我们事前不知道需要多大的内存,而我们在C中编程的时候不能够动态的申请数组(不能在程序中这样写:int array[n],其中n是你上一个计算中得出的值)。有人说,我申请大点,不需要的用其他数据填,针对我们现在的PC机貌似可以,但是为了节省空间和程序的优化性能考虑,我们需要能够动态的申请需要的空间。所以我们需要一个方能,能够动态申请空间。
C语言中malloc函数为我们提供了这样的方法,但是这些申请的空间的地址不是连续的。不能像数组那样通过 数组名+索引值 定位元素信息。但是我们可以通过一个方法将不连续的内存空间让我们感觉是连起来的。很简单,只要对每个数据附加一个信息,即下一个数据的位子信息即可。所以实现了随机分配的内存存放数据的连续性。
这样的结构叫 链表!
链表结构:
之前谈到链表的结构,可以大概知道其中有两个元素:一个是数据,一个是下一个该结构的地址。所以普遍的链表结点定义如下:
typedef struct Node { int data; struct Node* next; }ltNode,*ptNode;
如果看不懂这个结构定义,请参考关键字: struct、typedef
链表的基本操作
初始化链表
在初始化链表的时候,范了一个错误。先把错误的代码贴出来:
错误代码为:
1 void init_list(ptNode head) 2 { 3 printf("list init\n"); 4 head = (ptNode)malloc(sizeof(ltNode)); 5 head->data = 2; 6 printf("init- head->data = %d\n",head->data); 7 }
主函数重调用该函数:
ptNode plist = NULL;
init_list(&plist);
这样做的后果是出现错误。虽然打印出了init-head->data = 2,但是在主函数中却不能使用plist。
后来发现错误原因:(指针没理解深刻)
1.首先要知道malloc返回的是开辟空间的指针,(ptNode) malloc (sizeof(ltNode));(ptNode)是强制将malloc返回的地址转化为它的类型。
2.调用的时候是将plist传给初始化函数。本意是希望主函数plist能够访问到init_list函数中生成的头节点。
当时想,这是地址传递,应该是没有问题的。但是在主函数访问plist时候,出现错误,调试之后发现,plist是地址值为NULL。
也就是说,在init_list函数中,plist并没有指向malloc产生的地址。
综合以上两点看看:
初始化函数中,malloc返回给head的是新开辟的空间地址。我们希望主函数中的plist能够使用这个地址。因为要获取子函数中malloc产生的地址,我们就需要使用 地址传值 的方法。就需要函数的实参是地址的地址,形参是二级指针。这有就能够使主函数中的plist指向了init_list中malloc出来的地址。
正确代码:
1 void init_list(ptNode* head) 2 { 3 *head = (ptNode)malloc(sizeof(ltNode)); 4 (*head)->data = 0; 5 (*head)->next = NULL; 6 }
使用一个二级指针,这有就可以修改指针的指针了。
主函数调用:
init_list(&plist)
至此,链表的初始化完成。
建立链表
这里我建立一个含有十个元素的链表。用数组代替
#define N 10 int ary[N]={76,43,2,676,13,34,88,8,65,23};
建立链表比较简单,只需要两步骤:
1.开辟一个node(节点),把对应信息存入其中。
2.将链表中最后一个元素的指针指向新开辟的node即可。
1 void create_list(ptNode list) 2 { 3 ptNode p = NULL; 4 ptNode q = NULL; 5 6 int i; 7 q = list; 8 for(i=0;i<N;i++) 9 { 10 11 p = (ptNode)malloc(sizeof(ltNode)); 12 p->next = NULL; 13 p->data = ary[i]; 14 q->next = p; 15 q = p; 16 } 17 }
打印链表的函数:
1 void prt_list(ptNode list) 2 { 3 ptNode p = list; 4 while(p != NULL) 5 { 6 printf("%d\t",p->data); 7 p=p->next; 8 } 9 printf("\n"); 10 }
打印结果如下:
第一个10是头节点的,存放链表的长度。
链表的插入
编写这部分代码的时候,最好画一个图,这样编写程序方便简洁。
这幅图的意思是在节点1 和 2 间插入 节点3。
1.首先寻找到要插入节点(这里是1),地址为p(类型是ptNode)。则该节点下一个节点(这里是2)就是p->next(类型是ptNode)。
2.待插入的节点(这里3),地址为n,可以同过malloc生成。
3.如绿色线操作,完成编号1,2 动作。
错误做法:
红色1:p->nex = n;
红色2:n->next = p->next;
这有做会出现错误。完成红色色1之后,p->next 指向了n,在绿色2中就是自己指向了自己。
正确做法:只需要颠倒一下顺序就可以了。
代码如下:
1 int insert_list(ptNode list,int index,int idat) 2 { 3 int tlen = list->data; 4 if (index > tlen) 5 { 6 printf("error! can't insert in index\n"); 7 return -1; 8 } 9 ptNode p; 10 p = list; 11 while (index > 0) 12 { 13 p=p->next; 14 index--; 15 } 16 ptNode q = p->next; 17 ptNode n = (ptNode)malloc(sizeof(ltNode)); 18 19 n->data = idat; 20 n->next = p->next; 21 p->next = n; 22 23 24 list->data = list->data+1; 25 return 1; 26 }
调用:
insert_list(plist,3,11); prt_list(plist);
在第三个元素后面,插入一个元素11。
第三个元素后面插入的11。
元素的删除
同样,编写这部分代码,画图比较容易:
我要删除节点2,
1.指向它的前一个节点(1),要删除的节点就是p->next(2),之后的一个节点是p->next->next(3)。
2.使第一个节点的指向第三个,这有第二个就删除了。
3.free(p->next)
1 int delete_list(ptNode list,int index) 2 { 3 ptNode p = list; 4 if (index > list->data) 5 { 6 printf("error in delete\n"); 7 return -1; 8 } 9 while (index > 1) 10 { 11 p=p->next; 12 index--; 13 } 14 ptNode d = p->next; 15 p->next = p->next->next; 16 free(d); 17 list->data = list->data-1; 18 return 1; 19 }
删除第三个元素:
调用:
delete_list(plist,3); printf("delete elem...\n"); prt_list(plist);
输出结果:
第三个元素2被删除了。
到这里,单链表最基本的三个功能实现了。后面比如,单链表的清除,销毁,长度,获取指定位置的元素,查找元素,前驱,后驱等等都可以实现。