穷究链表(六)
这篇会开始进行链表函数的实现,这里先实现两个函数。
一开始考虑最容易实现的,挑选printlist来实现,printlist只需要遍历整个链表,然后将遍历到的节点的值逐个输出。
那就来考虑这样的问题,我定义一个临时的指针tmp,指向链表的第一个节点。如果tmp为NULL,那就说明链表连第一个节点都没有,也就是说这个链表是一个空表。如果tmp->next为NULL,那就说明tmp现在指向的节点是链表的最后一个节点,其没有next的链表节点了。
对于链表节点的输出操作:
我遍历整个链表,每遍历到一个节点,就将其进行输出,直到最后一个节点。当我遍历到最后一个节点,也就是tmp->next为NULL时,此时还需要继续进入循环,来输出最后一个节点,当输出最后一个节点之后,不需要再进入循环。而此时tmp=tmp->next,tmp的值为NULL,所以循环的判断跳出条件为tmp为NULL。如果tmp!=NULL,则继续进行循环。
那是否需要判断空链表呢?由于空链表也是tmp为NULL,此时不会进入循环,不会进行任何动作,所以不需要对空链表的情况做特殊处理。
当然,如果对于空链表,需要输出一个“empty linked list”,则就需要进行一个额外的判断了,这里的处理是输出一个空行。
代码片断如下:
2{
3 listnode* tmp=llist;
4 while (tmp)
5 {
6 printf("%d ",tmp->data);
7 tmp=tmp->next;
8 }
9 printf("\n");
10}
11
这样的实现就可以了,但是上面是输出一个空行,下面实现是输出“empty linked list”。
也可以使用下面的实现:
片段3比片段2要在循环中少掉一行代码,这样应该会减少一点计算时间。不过也相差不多啦。反正也不是数量级的改变。
同时,这里注意一下参数的问题。因为不需要修改,只是浏览,所以这里使用的只是指针,而不是指针的指针来做。
printlist实现结束,下面来实现添加节点的操作:
添加节点操作就要比上面的遍历操作要复杂一些了,我们需要在指定的位置pos之前添加一个节点,因为是在某个节点之前,所以我们只需要知道其之前的那个节点(pos-1),然后将其串起来即可,此时加入的新节点就会代替掉pos位置,而原来pos位置的节点则成为(pos+1)。
这里,如果要串起来,则就需要前面有一个节点,然后将它的next指针指向新的节点(当然,在此之前,需要将它原来的指向给新的节点,这样才能够串起来)。那如果没有前面的节点呢?
由于这里我们需要修改其内容(当为空表,或者插入位置为第一个节点时),所以需要使用到指针的指针。
根据上面的分析,再通过画图(画图都是手绘的,所以这个过程这里就省略了,直接将结果写在这里,而图在每一本数据结构书中应该都有,不过建议还是自己来画一下体会一下)得到:
可以分为5种情况,但是在画图之后,发现最终可以综合为2种情况。
1. 空表,插入新节点
2. 只有一个节点,插入新节点在第一个节点之后
3. 有两个节点(或更多,情况相同),插入节点在两个节点之间
4. 有两个节点(或更多),插入新节点在最后
5. 有一个或多个节点,插入节点在第一个节点之前
两种情况中第一种情况为1和5,第二种情况对应的是2,3,4。也就是插入第一个节点的情况和插入其他位置的情况要分开处理。
经过梳理之后,最后得到的代码如下:
如果是要插在第一个的位置上,则进入if,否则进入else,先定位到需要插入的节点,然后执行插入。这里如果pos比链表要大,则插入在最后,这里并没有进行错误处理的代码。
这里addNode由于要定位节点,然后执行插入,所以插入动作为O(1),但是整个算法复杂度为O(n)。
此时,编写的main函数如下
2int i;
3listnode *node;
4for (i=0;i<8;i++)
5{
6 node = (listnode*)malloc(sizeof(listnode));
7 node->next=NULL;
8 node->data=i;
9
10 addNode(&header,node,1);
11}
12
因为main中使用了malloc来分配空间,所以肯定要有free来与其进行对应。而free应该就是实现在deleteNode中的。其实这样,就是库的调用者来实现内存的分配,而库本身来实现内存的回收。这样并不一定不好,不过单纯由库来进行内存的分配和回收也是一种方法,而且这样的好处是都是由库来控制,不容易发生内存泄露的问题。