博客
关于我
(第12章)使用结构和链表
阅读量:393 次
发布时间:2019-03-05

本文共 6198 字,大约阅读时间需要 20 分钟。

文章目录

1.链表

  • 链表中的每个节点通过指针连接在一起,程序通过指针来访问链表中节点
  • 通常,节点是动态分配的

2.单链表

  • 链表的最后一个节点的指针字段的值为NULL,提示链表后面不再有其他节点
  • 根指针root pointer:是链表的起始位置,根指针指向链表的第1个节点。注意:根指针只是一个指针,它不包含任何数据。
  • 节点声明创建的结构如下:
typede struct NODE{   	struct NODE *link;	int value;}node;
  • 单链表的图如下所示:
    在这里插入图片描述

3.将新节点插入到一个有序的单链表中?

  • 方法1:大多数情况下,这已经是最佳的方案
    在这里插入图片描述
    链表的最终样子
    在这里插入图片描述
//若插入的是3,下面的代码是不能改变root的。//下面是用node的遍历方法,root指的是:指向一个Node的指针result=sll_insert(root,12);#include 
#include
#include "sll_node.h"#define true 1#define flase 0int sll_insert(Node *current,int new_value){ Node *previous; Node *New; //寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点 while (current!=NULL && current->value
link; } //为新增节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回False New=(Node *)malloc(sizeof(Node)); if (NULL==New) return false; New->value=new_value; //把新节点插入到链表中,并返回true new->link=current; previous->link=new; return true;}//下面的代码是指向node的指针的指针的遍历方法编程要点:(1)while循环不要越过链表的尾部,对一个NULL指针执行间接访问的操作(2)若把value=3插入到链表中,会发生什么?为了在链表的起始位置插入一个节点,函数必须修改根指针。但是,函数不能访问变量root。所以,可以把一个指向root的指针作为参数传递给函数,然后使用间接访问,函数不仅可以获得root的值(根指针),也可以向他存储一个新的指针值。(3)若将value=12插入,若按照顺序访问链表。当到达15的node就会停下来,但是前一个节点的指针字段必须进行修改以实现插入。解决的办法就是:始终保存一个指向链表当前节点之前的那个节点的指针。//root指的是:指向一个Node的指针,所以参数类型是:Node **result=sll_insert(&root,12);#include
#include
#include "sll_node.h"#define 0#define TRUE 1int sll_insert(Node **rootpt, int value){ Node *current; Node *previous; Node *new; //得到指向第一个节点的指针 current=*rootpt; previous=NULL;//检查新值,是否应为链表的第1个节点 //寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点 while (current !=NULL && current->value < value) { previous=current; current=current->link; } //为新增节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回False new=(Node *)malloc(sizeof(Node)); if (new==NULL) return false; new->value=value; //把新节点插入到链表中,并返回TRUE new->link=current; //检查新值是否应该被添加到链表的起始位置,若是,则修改根指针,使他指向新节点 if (previos==NULL)//将一个节点插入到链表的起始位置作为一种特殊情况进行处理 *rootpt=new;//new成为一个新root,但是new的value却没了啊。。。。 else previous->link=new; return TURE;}
  • 方法2:优化
    在这里插入图片描述
    下面是第2个节点和指向它的指针,若新值要插入到第2个节点之前,那么这个指针必须修改,我们只考虑指向这个节点的指针,至于哪个节点包含这个指针,则无关紧要。对于链表中的其他节点,都可以应用这个模式!!
    在这里插入图片描述
编程要点:(1)对于任何节点,对指针进行修改时,实际修改的是前一个节点的link字段(2)消除上面if (previos==NULL)的异常情况的关键在于:要意识到,链表中的每个节点都有一个指向它的指针。对于第一个节点,这个指针是根指针,对于其他节点,这个指针是前一个节点的link字段!!(3)优化的关键:这里的rootpt并不是指向节点本身,而是指向节点内部的link字段,即:必须能够取得当前节点的link字段的地址。//root指的是:指向一个Node的指针,所以参数类型是:Node **result=sll_insert(&root,12);//插入到一个有序单链表,函数的参数是一个指向链表第一个节点的指针,以及一个需要插入的新值#include 
#include
#include "sll_node.h"#define FALSE 0#define TRUE 1//在函数的指针变量中增加register的声明,用于提高代码的效率int sll_insert(Node **rootpt, int value){ register Node *current; register Node *new; //寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点 current=*rootpt; while (current!=NULL && current->value
link);//rootpt为指向当前节点的link字段 current=*rootpt;//current指向下一个节点,current=current->link;这样写好理解这行代码所要干的事情 } /* 等价于下面的写法: while ((current=*rootpt)!=NULL && current->value
link); } */ //为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回FALSE new=(Node *)malloc(sizeof(Node)); if (new==NULL) return false; new->value=value; //在链表中插入新节点,并返回TRUE new->link=current; *rootpt=new;//这里的rootpt并不是指向节点本身,而是指向节点内部的link字段,new是新增node的自己的地址, new->link保存的是下一个node的地址 return TRUE;}
  • 从一个单链表中删除一个节点
函数的第一个参数是指向链表根指针的指针,第2个参数是一个指向欲移除的节点的指针。#include 
#include
#include
#include "singly_linked_list_node.h"#define true 1#define false 0int sll_remove(struct NODE **rootp, struct NODE *delete){ register struct node *current; assert(node!=NULL); while ((current=*rootp)!=NULL && current!=delete) { rootp=&(current->link); } if (current==delete) { *rootp=current->link; free(delete); return true; } else return false;}

4.将一个值插入到一个有序的双链表中

  • 在一个双链表中,每个节点都包含两个指针:指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方向遍历双向链表。
  • 根节点的fwd字段指向链表的第1个节点,根节点的bwd字段指向链表的最后一个节点。若链表为空,则这两个字段都为NULL。
  • 链表第一个节点的bwd字段和最后一个节点的rwd字段都是NULL
节点类型的声明如下:typedef struct NODE{   	struct NODE *fwd;	struct NODE *bwd;	int value;}Node;

在这里插入图片描述

  • for循环终止之后的几个变量地状态如下:
    在这里插入图片描述
编写程序要点:(1)dll_insert函数接受两个参数:一个指向根节点的指针和一个整型值(2)dll_insert只有当预先插入的值原先不存在于链表中,才将其插入(3)一开始,函数使this指向根节点,next指针始终指向this之后的那个节点。它的思路是这两个指针同步前进,直到新节点应该插入到这两者之间。for循环检查next所指节点的值,判断是否达到需要插入的位置。如果在链路中找到新值,函数就简单地返回。否则,当到达链表尾部或者找到适当地插入位置时,循环终止。在任何一种情况下,新节点都应该插入到this所指地节点后面。/*把一个值插入到一个双链表中,rootp是一个指向根节点的指针,value是欲插入的新值。返回值:如果预插值原先已存在于链表中,函数返回0;如果内存不足,导致无法插入,函数返回-1;如果插入成功,函数返回0;*//*把一个节点插入到一个链表时,可能出现4种情况:(1)新值可能插入到链表的中间位置(2)新值可能插入到链表的起始位置(3)新值可能插入到链表的结束位置(4)新值可能既插入到链表的起始位置,又插入到链表的结束位置(即:原链表为空)*/#include 
#include
#include "double_linked_list_node.h"int dll_insert(Node *rootp,int value){ Node *this; Node *next; Node *newnode; /* 查看value是否已存在于链表中,如果是就返回。 否则,为新值创建一个新节点newnode。 ”this“将指向应该在新节点之前的那个节点,”next“将指向应该在新节点之后的那个节点 this=rootp; while ((next=this->fwd)!=NULL) { if (next->nalue==value) return 0; if (next->value>value) break; this=next; } */ for (this=rootpt;(next=this->fwd)!=NULL;this=next) { if (next->value==value) return 0; if (next->value > value) break; } /* 在我们决定新值是否应该插入到链表之前,并不为它分配内存。 如果事先分配内存,若发现新值原先已经存在于链表中,就有可能发生内存泄漏。 */ newnode=(Node *)malloc(sizeof(Node)); if (newnode==NULL) return -1; newnode->value=value; //把新值添加到链表中 if (next!=NULL) { //对于情况1或情况2,并非位于链表的尾部 if (this!=rootp)//情况1:若不在链表的起始处 { newnode->fwd=next; this->fwd=newnode; newnode->bwd=this; next->bwd=newnode; } else//情况2:位于链表的起始位置 { newnode->fwd=next; rootp->fwd=newnode; newnode->bwd=NULL; next->bwd=newnode; } } else//情况3或4:位于链表的尾部 { if (this!=rootp)//情况3:并非位于链表的起始位置 { newnode->fwd=NULL; this->fwd=newnode; newnode->bwd=this; rootp->bwd=newnode; } else//情况4:位于链表的起始位置 { newnode->fwd=NULL; rootp->fwd=newnode; newnode->bwd=NULL; rootp->bwd=newnode; } } return 1;}

在这里插入图片描述

5.总结

  • 单链表

    (1)链表中的每个节点包含一个节点,用于指向链表的下一个节点;
    (2)独立的根指针指向链表的第1个节点
    (3)每个节点采用动态分配内存的方式
    (4)遍历链表是根据指针进行的,单链表只能以一个方向进行遍历
    (5)将一个新值插入到一个有序的单链表过程:将新节点的link字段设置为指向它的的目标后续节点,其次,前一个节点的link字段必须设置为指向这个新节点。当时,该技巧,会使得插入到链表的起始位置成为一种特殊情况!!在C语言中,可以通过保存一个指向必须进行修改的link字段的指针,而不是保存一个指向一个节点的指针,从而消除这种特殊情况。

  • 双链表

    (1)有两个link字段:其中一个指向链表的下一个节点,另一个指向链表的前一个节点
    (2)双链表有两个根指针,分别指向第1个节点和最后一个节点。因此,遍历双链表可以从任何一端开始,而且在遍历过程中可以改变方向
    (3)将一个新值插入到双链表的过程:新节点的前向和后向link字段必须被设置,前一个节点的后向link字段和后一个节点的前向link字段也必须进行修改,使他们指向这个新节点。

参考:<C和指针>

转载地址:http://kmyzz.baihongyu.com/

你可能感兴趣的文章
MSSQL数据库迁移到Oracle(二)
查看>>
MSSQL日期格式转换函数(使用CONVERT)
查看>>
MSTP多生成树协议(第二课)
查看>>
MSTP是什么?有哪些专有名词?
查看>>
Mstsc 远程桌面链接 And 网络映射
查看>>
Myeclipse常用快捷键
查看>>
MyEclipse更改项目名web发布名字不改问题
查看>>
MyEclipse用(JDBC)连接SQL出现的问题~
查看>>
mt-datetime-picker type="date" 时间格式 bug
查看>>
myeclipse的新建severlet不见解决方法
查看>>
MyEclipse设置当前行背景颜色、选中单词前景色、背景色
查看>>
Mtab书签导航程序 LinkStore/getIcon SQL注入漏洞复现
查看>>
myeclipse配置springmvc教程
查看>>
MyEclipse配置SVN
查看>>
MTCNN 人脸检测
查看>>
MyEcplise中SpringBoot怎样定制启动banner?
查看>>
MyPython
查看>>
MTD技术介绍
查看>>
MySQL
查看>>
MySQL
查看>>