[align=left]链表在数据结构中讲得太多,但是都是炒来炒去没有一个是具有高移植性、易理解性等,以至于很多学习C语言的学生看到C语言都是畏惧三分,[/align] [align=left]朱兆祺这节带着你且看如何写出高效率、通俗易懂的单向链表,看完这节,你要是没有撕掉你手中的数据结构书本,那就是我写得还不够好,那么[/align] [align=left]请你联系我,我再改,直到你有撕掉你手中数据结构书本的感觉为止。[/align] typedef unsigned int ElementType ; [align=left]/* 数据类型定义 */[/align] typedef struct _SingleListNode {
ElementType data ;
struct _SingleListNode *next ; [align=left]}SingleListNode;[/align] [align=left][/align] typedef SingleListNode* SingleList ; typedef SingleListNode* Position ;
[align=left]首先要创建一个头节点,并在内存中为头节点动态分配存储空间。[/align] [align=left]/* 申请存储空间,建立头节点 */[/align] SingleListNode *pHead = (SingleListNode*)malloc( sizeof(SingleListNode) ) ; 将pHead定义为(SingleListNode *)类型,malloc函数的原型为void *malloc( size_t size ); 函数指向一个大小为size的空间,存储空间的指针必须为堆,不能是栈。因为不知道开辟的空间是什么类型,所以malloc函数返回的是(void*)类型,再通过强制转换为我们想要的类型。由于data占4个字节,next占4个字节,所以sizeof(SingleListNode) = 8。所以*pHead所指向的数据类型(即为链表节点)占用8个字节。
[align=left]#if 1[/align] printf("sizeof(SingleListNode) = %d \n" , sizeof(SingleListNode) ) ; printf("sizeof(*pHead) = %d \n" , sizeof(*pHead) ) ; printf("sizeof(pHead) = %d \n" , sizeof(pHead) ) ; [align=left]#endif[/align] [align=left]输出结果为:8,8,4。[/align] [align=left]创建链表即要将data和next置空。[/align] pHead->next = NULL ; //将头节点指针域置空 pHead->data = NULL ; //将头节点数据域置空 [align=left][/align] [align=left]在将节点插入到链表之前,首先得为这个待插入的节点申请存储空间,并且建立该节点的指针ptr。[/align] SingleListNode *ptr = (SingleListNode *)malloc( sizeof(SingleListNode) ) ; [align=left]这里ptr为待插入节点的指针,ptr的值即为待插入链表的节点的地址。*ptr即为节点中存储的数据:data和next,共占8个字节。[/align] [align=left]待插入节点插入链表第一步,将数据x保存到ptr->data。[/align] [align=left]ptr->data = x ;[/align] [align=left]待插入节点插入链表第二步,将pos->next赋给ptr->next。[/align] [align=left]ptr->next = pos->next ;[/align] [align=left]待插入节点插入链表第三步,将pos->next指向待插入节点。[/align] [align=left]pos->next = ptr ;[/align] [align=left]这样我们就完成了将数据x的节点插入到链表中。[/align] [align=left]#if 1[/align] printf(" x = %c \n" , x ) ; printf("&x = %#x \n" , &x ) ; printf(" ptr = %#x \n" , ptr ) ; printf("&ptr = %#x \n" , &ptr ) ; printf("(pos->next) = %#x \n" , (pos->next) ) ; printf("(ptr->next) = %#x \n\n" , (ptr->next) ) ; [align=left]#endif[/align] [align=left]在插入节点函数中加入调试代码,运行程序,输入abc,调试结果。[/align] [align=left]请输入链表节点数据: abc[/align] [align=left]x = a[/align] [align=left]&x = 0x12fe3c[/align] [align=left]ptr = 0x392920[/align] [align=left]&ptr = 0x12fe24[/align] [align=left](pos->next) = 0x392920[/align] [align=left](ptr->next) = 0[/align] [align=left][/align] [align=left]x = b[/align] [align=left]&x = 0x12fe3c[/align] [align=left]ptr = 0x392958[/align] [align=left]&ptr = 0x12fe24[/align] [align=left](pos->next) = 0x392958[/align] [align=left](ptr->next) = 0[/align] [align=left][/align] [align=left]x = c[/align] [align=left]&x = 0x12fe3c[/align] [align=left]ptr = 0x392990[/align] [align=left]&ptr = 0x12fe24[/align] [align=left](pos->next) = 0x392990[/align] [align=left](ptr->next) = 0[/align] [align=left][/align] [align=left]ListData: abc[/align] [align=left]ListAddress: 0x394b48 0x392920 0x392958 0x392990[/align] [align=left]通过上面的调试,我们将数据和地址放进节点,便于我们明白链表的储存。[/align] [align=left]可以看到pos->next是和ptr相等的,因为两者都是指向待插入节点,而ptr->next始终是为0,因为ptr->next是指向NULL。[/align] [align=left]从中我们还可以看出,链表的存储空间是间断的,而不是像数组元素存储空间是连续的。[/align] [align=left]/*************************************************************************[/align] [align=left]** 函数名称 :sll_FindPrevious[/align] [align=left]** 函数功能 :单向链表按值搜索位置的前驱算法函数[/align] ** 入口参数 :pList , [align=left]** 出口参数 : pCur[/align] [align=left]*************************************************************************/[/align] Position sll_FindPrevious(SingleList pList , ElementType x) [align=left]上面这个函数得明白什么叫做函数指针,什么叫做指针函数。[/align] [align=left]void *Func () :指针类型的函数[/align] [align=left]void (*Func)() :一个指向void型函数的指针[/align] [align=left]定义一个结构体指针pCur,使其总是指向当前搜索节点的前一个节点。初始状态下pCur是指向头结点。[/align] [align=left]Position pCur ;[/align] pCur = pList ; //pCur的初值指向头节点 [align=left]从第一个节点开始比较pCur指向下一个节点的数据域和数据x的值,如果两者相等,则返回pCur;否则pCur重新指向下一个节点。[/align] while ( (pCur->next) && (pCur->next->data) != x) { pCur = pCur->next ; //pCur重新指向下一个节点 [align=left] }[/align] return pCur ; //返回已找到元素x所在节点的地址 [align=left]删除节点和插入节点是相对应的,在删除节点之前,我们先要找到需要删除节点的位置,此时,我们就可以使用按值搜索节点位置的前去算法找到需要删除的节点位置。[/align] [align=left]/*************************************************************************[/align] [align=left]** 函数名称 :sll_delete[/align] [align=left]** 函数功能 :删除节点函数[/align] [align=left]** 入口参数 :pList ,x[/align] [align=left]** 出口参数 :void[/align] [align=left]*************************************************************************/[/align] void sll_delete(SingleList pList , ElementType x) Position pCur , temp ; //pCur指向要删除节点的上一个节点,temp指向删除的节点 pCur = sll_FindPrevious(pList , x) ; //返回需要删除的节点的上一个节点地址 [align=left]如果sll_FindPrevious能返回删除节点的位置(即pCur不为NULL),那么执行删除该节点的操作。[/align] if ( pCur && (pCur->next) ) [align=left]删除操作第一步,让temp指向待删除的节点。[/align] [align=left]temp = pCur->next ;[/align]
[align=left]删除操作第二步,让pCur->next指向待删除节点的下一个节点,这样就把待删除节点*temp删除。[/align] [align=left]pCur->next = temp->next ;[/align] [align=left]最后将已经删除了的节点temp的存储空间释放掉。[/align] [align=left]free(temp) ; [/align] [align=left]在删除节点函数中添加调试代码,在删除节点前和删除节点后都加入,进行前后对比。[/align] [align=left]#if 1[/align] [align=left] printf("pCur = %#x \n" , pCur) ;[/align] [align=left] printf("pCur->next = %#x \n" , pCur->next) ;[/align] [align=left] sll_printAddress(pList) ;[/align] [align=left] printf("\n\n") ;[/align] [align=left]#endif[/align] [align=left][/align] [align=left] if ( pCur && (pCur->next) ) {[/align] [align=left] temp = pCur->next ; [/align] [align=left] pCur->next = temp->next ; [/align] [align=left] free(temp) ; [/align] [align=left] }[/align] [align=left][/align] [align=left]#if 1[/align] [align=left] printf("pCur = %#x \n" , pCur) ;[/align] [align=left] printf("pCur->next = %#x \n" , pCur->next) ;[/align] [align=left] sll_printAddress(pList) ;[/align] [align=left] printf("\n\n") ;[/align] [align=left]#endif[/align] [align=left]调试代码的输出。[/align] [align=left]请输入链表节点数据: abc[/align] [align=left][/align] [align=left]ListData: abc[/align] [align=left]ListAddress: 0x394b48 0x392920 0x392958 0x392990[/align] [align=left][/align] [align=left]输入要删除的字符: b[/align] [align=left][/align] [align=left]pCur = 0x392920[/align] [align=left]pCur->next = 0x392958[/align] [align=left]0x394b48 0x392920 0x392958 0x392990[/align] [align=left][/align] [align=left]pCur = 0x392920[/align] [align=left]pCur->next = 0x392990[/align] [align=left]0x394b48 0x392920 0x392990[/align] [align=left][/align] [align=left]ListData: ac[/align] [align=left][/align] [align=left]请按任意键继续. . .[/align] [align=left]通过前后比较可以知道,我们删除了0x392958这个地址的节点,即为储存b的节点。[/align] [align=left]如果这个链表不再需要使用,则要把这个链表销毁掉。销毁办法是一个一个节点释放掉。[/align] [align=left]销毁链表第一步,让待销毁的节点指针(ptr)指向头节点(初始时,pList是指向头结点)。[/align] [align=left]SingleListNode *ptr = pList ;[/align] [align=left]销毁链表第二步,让现在pList所指的节点的下一个节点成为头节点,因为现节点将要被删除,直到所指节点为空为止。[/align] [align=left]pList = pList->next ;[/align] [align=left]销毁链表第三步,释放掉待销毁节点。[/align] [align=left]free(ptr) ;[/align]
[align=left]销毁链表第四步,让下一个节点(即为pList现在所指节点)成为待删除节点。[/align] [align=left]ptr = pList ;[/align] [align=left]这样往复循环,直到ptr指向NULL为止。这样就成功把一个链表销毁了。[/align] [align=left][/align] [align=left]在销毁链表函数中加入调试代码,运行。[/align] [align=left]#if 1[/align] [align=left] printf("ptr = %#x \n" , ptr) ;[/align] [align=left] sll_printAddress(pList) ;[/align] [align=left] printf("\n\n") ;[/align] [align=left]#endif[/align] [align=left]运行结果,[/align] [align=left]ptr = 0x394b48[/align] [align=left]0x392920 0x392990 0x3929c8[/align] [align=left][/align] [align=left]ptr = 0x392920[/align] [align=left]0x392990 0x3929c8[/align] [align=left][/align] [align=left]ptr = 0x392990[/align] [align=left]0x3929c8[/align] [align=left][/align] [align=left]ptr = 0x3929c8[/align] [align=left][/align] [align=left]请按任意键继续. . .[/align] [align=left]我们可以看到当前删除的节点地址ptr,和删除节点之后各节点的地址。[/align] [align=left][/align]
|
|
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理