为什么要学习链表?

链表主要有以下几大特性:

1、解决数组无法存储多种数据类型的问题。

2、解决数组中,元素个数无法改变的限制(C99的变长数组,C++也有变长数组可以实现)。

3、数组移动元素的过程中,要对元素进行大范围的移动,很耗时间,效率也不高。

先来感性的认识一下链表,我们先来认识下简单的链表:

从这幅图我们得出以下信息:

这个简单链表的构成:

头指针(Header),若干个节点(节点包括了数据域和指针域),最后一个节点要指向空。

实现原理:头指针指向链表的第一个节点,然后第一个节点中的指针指向下一个节点,然后依次指到最后一个节点,这样就构成了一条链表。

接下来看看链表的数据结构:

struct  list_node

{

int data ; //数据域,用于存储数据

struct list_node *next ; //指针,可以用来访问节点数据,也可以遍历,指向下一个节点

};

那么如何来创建一个链表的一个节点呢?

我们写个程序演示一下:

#include

#include

#include

struct  list_node

{

int data ;

struct list_node *next ;

};

typedef struct list_node list_single ;

int main(void)

{

list_single *node = NULL ;          //1、首先,当然是定义一个头指针

node = (list_single *)malloc(sizeof(list_single)); //2、然后分配内存空间

if(node == NULL){

printf("malloc fair!\n");

}

memset(node,0,sizeof(list_single)); //3、清一下

node->data = 100 ;     //4、给链表节点的数据赋值

node->next = NULL ;                 //5、将链表的指针域指向空

printf("%d\n",node->data);

free(node);

return 0 ;

}

那么,这仅仅只是创建一个链表中的一个节点,为了好看,我们把创建节点封装成函数,以后想创建多少个节点,我们就可以反复调用一个函数来创建,会很方便:

list_single *create_list_node(int data)

{

list_single *node = NULL ;

node = (list_single *)malloc(sizeof(list_single));

if(node == NULL){

printf("malloc fair!\n");

}

memset(node,0,sizeof(list_single));

node->data = 100 ;

node->next = NULL ;

return node ;

}

接下来在程序上完成的程序:

#include

#include

#include

struct  list_node

{

int data ;

struct list_node *next ;

};

typedef struct list_node list_single ;

list_single *create_list_node(int data)

{

list_single *node = NULL ;

node = (list_single *)malloc(sizeof(list_single));

if(node == NULL){

printf("malloc fair!\n");

}

memset(node,0,sizeof(list_single));

node->data = data;

node->next = NULL ;

return node ;

}

int main(void)

{

int data = 100 ;

list_single *node_ptr = create_list_node(data); //创建一个节点

printf("node_ptr->data=%d\n",node_ptr->data);   //打印节点里的数据

printf("node_ptr->next=%d\n",node_ptr->next);

free(node_ptr);

return 0 ;

}

执行结果 :

这样我们就完成一个链表节点的创建了,那么它现在的样子如下图:

链表的结构里,数据存储了100,因为这个链表只有一个节点,所以它的指针域指向了NULL。

上面只是建立一个单链表的基本雏形,接下来咱们再来增加一点难度。如果创建多个单链表节点,实现单链表的增删改查?把单链表应用起来。

1、首先定义一个单链表的数据结构

创建节点函数原型可定义如下:

struct list *create_node(int data) ;

如何创建单链表的节点,主要分以下步骤:

(1)给当前的每个节点的数据结构配置定量的空间大小

ep : struct list *node = malloc(sizeof(struct list));

(2)清节点数据(由于结构体变量在未初始化的时候,数据是脏的)

ep : memset(node,0,sizeof(struct list));

(3)给节点初始化数据

ep : node->id = data ;

(4)将该节点的指针域设置为NULL

ep : node->next = NULL ;

2、单链表的尾插:

尾插节点函数原型可定义如下:

如何将当前链表和新的节点相连接?只要实现:

header->next = new

尾插流程如下:

(1)获取当前节点的位置,也就是访问头节点

ep : struct list *p = header ;

(2)判断是否为最后一个节点,如果不是,移动到下一个节点,如果是,将数据插入尾部。

ep : while(NULL != p->next) p = p->next ;

p->next = new ;

3、单链表的头插

很好理解,头插就是把新的节点插在原来的节点和原来节点的下一个节点之间的一个节点。如图,新的节点插在头节点和节点1。

所以可以推出头插流程如下:

(1)获取当前节点的位置,也就是访问头节点

ep : struct list *p = header ;

(2)新的节点的下一个节点设置为原来头节点的下一个节点(第一个节点)

ep : new->next = p->next ;

(3)原来的头节点的下一个节点设置为现在新插入的头节点

ep : p->next = new ;

4、单链表的遍历

如图为一条单向链表的模型,看图知道该链表由头节点和若干个节点组成,最后一个节点(尾节点)为NULL 。

从图中可以得出信息,如果我们要打印出各个节点的数据,要考虑以下问题:

(1)需要打印头节点吗?(头节点肯定是不用打印的,因为这是我们为了操作方便而设置的一个节点)。

(2)这条链表有多少个节点我们怎么知道?(通过判断该链表是否已经到达了尾节点,标志就是NULL)

那么可以得到流程如下:

(1)获取当前节点的位置,也就是访问头节点

ep : struct list *p = header ;

(2)由于头节点我们不需要去打印它,这时候,初始化打印的节点需要从第一个节点开始。

ep : p = p->next ;

(3)判断是否为最后一个节点,如果不是,先打印第一个节点的数据(1),然后移动到下一个节点(2),重复这两个步骤。

如果是最后一个节点,直接打印数据即可。

while(NULL != p->next){ printf("node:%d\n",p->data) ; p = p->next ;}

printf("node:%d\n",p->data);

当然还可以一句代码解决,这样就达到了先偏移,后取数据。

while(NULL != p->next){ p = p->next ; printf("node:%d\n",p->data) ; }

5、单链表的删除

删除节点的函数原型可定义如下:

int detele_list_node(struct list *pH , int data);

单向链表的删除要考虑两种情况,一种的普通节点的删除(当然,头节点不能算)

还有一种是尾节点的前一个节点的删除情况,注意,删除完节点还需要释放对应节点的内存空间。

删除节点的设计流程:

(1)先定义两个指针,一个表示当前的节点,另一个表示当前节点的上一个节点。

ep : struct list *p = header ;  //当前节点

struct list *prev = NULL ; //当前节点的上一个节点

(2)遍历整个链表,同时保存当前节点的前一个节点

ep : while(NULL != p->next)

{

//保存了当前的节点的前一个节点

prev = p ;

//保存当前偏移的节点

p = p->next ;

return 0 ;

}

(3)在遍历的过程中查找要删除的数据

ep : while(NULL != p->next)

{

//保存了当前的节点的前一个节点

prev = p ;

//保存当前偏移的节点

p = p->next ;

//查找到了数据

if(p->id == data)

{

}

return 0 ;

}

(4)查找到了数据后,分两种情况删除

ep : 普通节点的删除

if(p->id == data)

{

prev->next = p->next ;

free(p);

}

ep : 考虑尾节点的下一个节点为NULL的节点删除

if(p->id == data)

{

if(p->next == NULL)

{

prev->next = NULL ;

free(p);

}

}

6、单链表的逆序

逆序步骤:

单链表的基本操作流程咱们基本搞懂了,下面写一个程序,这将会变得非常非常的简单。

#include

#include

#include

typedef struct slist

{

int id ;

struct slist *next ;

}L;

//创建一个节点

L *create_node(int data)

{

//给每个节点分配结构体一样的空间大小

L *p = malloc(sizeof(L));

if(NULL == p)

{

printf("malloc error!\n");

return NULL ;

}

//由于结构体在未初始化的时候一样是脏数据,所以要清

memset(p,0,sizeof(L));

//初始化第一个节点

p->id = data ;

//将节点的后继指针设置为NULL

p->next = NULL ;

}

//链表的尾插

void tail_insert(L *pH , L *new)

{

//获取当前的位置

L *p = pH ;

//如果当前位置的下一个节点不为空

while(NULL != p->next)

{

//移动到下一个节点

p = p->next ;

}

//如果跳出以上循环,所以已经到了NULL的这个位置

//此时直接把新插入的节点赋值给NULL这个位置

p->next = new ;

}

//链表的头插

void top_insert(L *pH , L *new)

{

L *p = pH ;

new->next = p->next ;

p->next = new ;

}

//链表的遍历

void Print_node(L *pH)

{

//获取当前的位置

L *p = pH ;

//获取第一个节点的位置

p = p->next ;

//如果当前位置的下一个节点不为空

while(NULL != p->next)

{

//(1)打印节点的数据

printf("id:%d\n",p->id);

//(2)移动到下一个节点,如果条件仍为真,则重复(1),再(2)

p = p->next ;

}

//如果当前位置的下一个节点为空,则打印数据

//说明只有一个节点

printf("id:%d\n",p->id);

}

//删除链表中的节点

int detele_list_node(L * pH , int data)

{

//获取当前头节点的位置

L *p = pH ;

L *prev = NULL;

while(NULL != p->next)

{

//保存当前节点的前一个节点的指针

prev = p ;

//然后让当前的指针继续往后移动

p = p->next ;

//判断,找到了要删除的数据

if(p->id == data)

{

//两种情况,一种是普通节点,还有一种是尾节点

if(p->next != NULL)  //普通节点的情况

{

prev->next = p->next ;

free(p);

}

else //尾节点的情况

{

prev->next = NULL ; //将这个尾节点的上一个节点的指针域指向空

free(p);

}

return 0  ;

}

}

printf("没有要删除的节点\n");

return -1 ;

}

void trave_list(L * pH)

{

//保存第一个节点的位置

L *p = pH->next;

L *pBack;

int i = 0 ;

if(p->next == NULL || p == NULL)

return ;

while(NULL != p->next) //遍历链表

{

//保存第一个节点的下一个节点

pBack = p->next ;

//找到第一个有效节点,其实就是头指针的下一个节点

if(p == pH->next)

{

//第一个有效节点就是最后一个节点,所以要指向NULL

p->next = NULL ;

}

else

{

/*

new->next = p->next ;

p->next = new ;

*/

p->next = pH->next ; //尾部连接

}

pH->next = p ; //头部连接

p = pBack ; //走下一个节点

}

top_insert(pH,p); //插入最后一个节点

}

int main(int argc , char **argv)

{

//创建第一个节点

int i ;

L *header = create_node(0);

for(i = 1 ; i < 10 ; i++)

{

tail_insert(header,create_node(i));

}

Print_node(header);

detele_list_node(header,5);

putchar('\n');

Print_node(header);

putchar('\n');

trave_list(header);

Print_node(header);

return 0 ;

}

运行结果:

当然,单链表存在一定的弊端,就是查找数据和删除数据的时候比较麻烦,而双链表的出现就是为了解决它的弊端:

双链表的引入是为了解决单链表的不足:

(1)双链表可以往前遍历,也可以往后遍历,具有两个方向

双链表的节点 = 有效数据 + 两个指针(分别指向前一个节点和后一个节点)

双向链表的图形结构描述:

struct double_list                                   struct double_list

{                                                            {

数据域;                  ep :------->                   int data ;

指针域(前向指针) ;                                   struct double_list *prev ;

指针域(后向指针) ;                                   struct double_list *next ;

};                                                             };

1、双向链表的创建

struct list *create_node(int data) ;

创建步骤(与单链表类似,就是多了一个指针):

(1)给节点申请空间:

ep : struct double_list *p = malloc(sizeof(struct double_list));

(2)初始化数据域

ep : p->data = data ;

(3)初始化指针域

ep : p->prev = NULL ;

p->next = NULL ;

2、双向链表的尾插

双向链表尾插节点的函数可以定义如下:

void double_list_tail_insert(struct double_list *header , struct double_list *new) ;

尾插图示操作:

尾插的步骤:

(1)找到链表的尾节点

ep : 和单链表差不多

DL *p = header ;

while(NULL != p->next)

p = p->next ;

(2)将新的节点连接到尾节点的后面成为新的节点

1.原来的next指针指向新节点的首地址。            p->next = new ;

2.新节点的prev指针指向原来的尾节点的首地址。 new->prev = p;

3、双向链表的头插

双向链表头插节点的函数可以定义如下:

void double_list_top_insert(struct double_list *header , struct double_list *new) ;

4、双向链表的遍历

4.1 正向遍历

void double_list_for_each(DL *header)

步骤:和单链表完全一致,没什么好写的。

4.2 反向遍历

void double_list_for_each_nx(DL *header)

步骤:(1)和单链表一样,先循环找到最后一个节点的地址

(2)再依靠prev指针循环往前移动

2.1 先打印最后一个数据  ep : printf("%d ",p->data);

2.2 向前循环遍历         ep : p = p->prev ;

判断条件:header->prev != p->prev,

header保存的是头节点的地址,

header->prev保存的是头节点的prev的地址,

header->next保存的是头节点的next的地址,

头节点在创建的时候:

header->prev = NULL ;

header->next = NULL ;

所以这个条件这样写header->prev = NULL也是对的。

5、双向链表节点的删除

假设需要删除节点1:

首先:

(1)获取当前节点的地址:

ep : p = header;

(2)遍历所有的节点,找到要删除的节点:

ep :

while(NULL != p->next)

{

p = p->next ;

if(p->data == data)

{

}

}

(3)找到要删除的数据以后,需要做判断,判断两种情况,这和单链表差不多

3.1 如果找到当前节点的下一个节点不为空

3.1.1

那就把当前节点的prev节点指向要删除的这个节点的prev

因为当前的prev节点保存的是要删除的上一个节点的指针

p->next->prev = p->prev ;

3.1.2

然后将当前节点的prev指针(也就是上一个节点的指针)指向当前节点(要删除的)的下一个节点:

p->prev->next = p->next ;

3.1.3

最后释放删除指针的空间:

free(p);

3.2 如果找到当前节点的下一个节点为空

3.2.1

直接把当前指针(要删除的节点)的prev指针(保存着当前指针的上一个节点的地址)的下一个next指针设置为空。

p->prev->next = NULL ;

3.2.2

将删除的指针的空间释放:

free(p);

看来,双链表学起来比单链表容易多了!确实啊,多了一个方向,操作起来就更加容易了,但是多了一个方向,一维多了一个指针,相比增加了一定的复杂度,但是,只要牢记prev指针和next指针的指向,那么,手画一图,代码即可写出!

下面给一个案例实现一下双向链表:

#include

#include

#include

//创建一个双链表的数据结构

typedef struct __double_list

{

int data ;

struct __double_list *prev ;

struct __double_list *next ;

}DL ;

//创建双向链表并插入一个节点

DL *create_dl_node(int data)

{

DL *p = malloc(sizeof(DL));

if(NULL == p)

{

printf("create dl node fair!\n");

return NULL ;

}

//初始化数据

p->data = data ;

//初始化指针

p->next = NULL ;

p->prev = NULL ;

}

//双向链表的尾插

void double_list_tail_insert(DL *header , DL *new)

{

//取得当前节点的地址

DL *p = header ;

//找到链表的尾节点

while(NULL != p->next)

{

//找不到接着找

p = p->next ;

}

//找到了尾节点,指向新节点的首地址

p->next = new ;

//新节点的prev指针指向原来的尾节点的首地址。

new->prev = p;

}

//双向链表的头插(也就是插在两个节点之间的插入方式)

void double_list_top_insert(DL *header , DL *new)

{

//新节点的next指针指向原来的节点一的地址

new->next = header->next ;

//判断当前节点的下一个节点的地址是否为空

if(NULL != header->next)

header->next->prev = new ; //节点1的prev指针指向新节点的地址

header->next = new ;

new->prev = header ;

}

//双向链表的正向遍历

void double_list_for_each(DL *header)

{

DL *p = header ;

while(NULL != p->next)

{

p = p->next ;

printf("%d ",p->data);

}

}

//双向链表的反向遍历

void double_list_for_each_nx(DL *header)

{

DL *p = header ;

//先找到尾节点

while(NULL != p->next)

{

p = p->next ;

}

//已经找到了尾节点,向前遍历,注意,遍历到头节点之前

//限制条件: != 头结点

while(NULL != p->prev)

{

printf("%d ",p->data);

p = p->prev ;

}

}

//双向链表节点的删除

int double_list_delete_node(DL *header , int data)

{

//取得当前节点

DL *p = header;

//遍历所有的节点

while(NULL != p->next)

{

p = p->next ;

//找到了对应要删除的数据了

if(p->data == data)

{

//一样存在两种情况

//(1)当前节点的下一个节点不为空

if(p->next != NULL)

{

//那就把当前节点的prev节点指向要删除的这个节点的prev

//因为当前的prev节点保存的是要删除的上一个节点的指针

p->next->prev = p->prev ;

//还要指定它的next节点

p->prev->next = p->next ;

free(p);

}

//(2)当前节点的下一个节点为空

else

{

//把

p->prev->next = NULL ;

free(p);

}

return 0 ;

}

}

printf("\n没有找到对应要删除的节点,或者节点已经被删除!\n");

return -1 ;

}

int main(void)

{

int i ;

DL *header = create_dl_node(0);

for(i = 0 ; i < 10 ; i++)

{

//双向链表的尾插

//double_list_tail_insert(header,create_dl_node(i));

//双向链表的头插

double_list_top_insert(header,create_dl_node(i));

}

//双向链表的正向遍历

printf("双向链表的正向遍历:");

double_list_delete_node(header,5);

double_list_for_each(header);

// double_list_delete_node(header,9);

double_list_delete_node(header,5);

putchar('\n');

printf("双向链表的反向遍历:");

double_list_for_each_nx(header);

return 0 ;

}

运行结果:

是单向链表吗_一步一步教你从零开始写C语言链表相关推荐

  1. 一步一步教你从零开始写C语言链表(超详细)

    STM32 HAL开发完全指南 写文章 一步一步教你从零开始写C语言链表(超详细) 杨源鑫 嵌入式系统工程师.物联网创业合伙人,业务经理兼产品经理 285 人赞同了该文章 为什么要学习链表? 链表主要 ...

  2. 链表的数据域怎么使用结构体_一步一步教你从零开始写C语言链表

    为什么要学习链表? 链表主要有以下几大特性: 1.解决数组无法存储多种数据类型的问题. 2.解决数组中,元素个数无法改变的限制(C99的变长数组,C++也有变长数组可以实现). 3.数组移动元素的过程 ...

  3. java 链表反转_剑指BAT:如何最优雅着反转单链表?

    前言 以专题的形式更新刷题贴,欢迎跟我一起学习刷题,相信我,你的坚持,绝对会有意想不到的收获.每道题会提供简单的解答,如果你有更优雅的做法,欢迎提供指点,谢谢 [题目描述] 反转单链表.例如链表为: ...

  4. 代写python期末作业价格_代做program留学生作业、代写Python语言作业、代做algorithm课程作业、代写Python程序设计作业...

    代做program留学生作业.代写Python语言作业.代做algorithm课程作业.代写Python程序设计作业 日期:2020-01-09 10:13 Coursework Brief: ASS ...

  5. C语言链表返回第n个到最后的节点的算法(附完整源码)

    C语言链表返回第n个到最后的节点的算法 C语言链表返回第n个到最后的节点的算法完整源码(定义,实现,main函数测试) C语言链表返回第n个到最后的节点的算法完整源码(定义,实现,main函数测试) ...

  6. c++使用单向链表存储一组有序数据_《一起学习java和数据结构》系列-数组和链表...

    数组 数组是一个线性表数据结构.它用一段连续的内存地址空间,来存储一些相同类型的数据. 从上面的定义,我们不难看出几个关键词. 线性表:顾名思义,线性表就是数据排列成一条线的数据结构.每一个线性表只有 ...

  7. c语言新建一个单向链表菜鸟,【图片】菜鸟的进击——玩转C语言链表【c程序设计吧】_百度贴吧...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 链表是我们学习C语言避不开的问题,就让我们一起飞过去看看吧: 1 定义链表 链表是C语言编程中常用的数据结构,比如我们要建一个整数链表,一般可能这么定义: ...

  8. java链表实现_链表的原理及java实现

    一:单向链表基本介绍 链表是一种数据结构,和数组同级.比如,Java中我们使用的ArrayList,其实现原理是数组.而LinkedList的实现原理就是链表了.链表在进行循环遍历时效率不高,但是插入 ...

  9. 就地链表反转_链表常见问题总结(一)

    1.链表 链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer) 由于不必须按顺序存储,链表在插 ...

最新文章

  1. Linux学习之CentOS(十二)--crontab命令的使用方法
  2. c语言指针的自我评价,个人自我评价
  3. 直播预告丨爆款独立站如何利用数据提升经营效率?
  4. css fix 手机端,移动端布局fixed问题解决方案
  5. ActiveX、OLE和COM介绍
  6. 第10篇:Flowable-BPMN操作流程部署、启动
  7. linux进程cpu时间片,能讲一下在Linux系统中时间片是怎么分配的还有优先级的具体算法是...
  8. java.io 相关tips
  9. android 自定义特效,Android自定义FloatingText仿点赞+1特效
  10. 【codevs1867】【Tyvj3508】【BZOJ1041】圆上的整点,数学乱搞
  11. HDU-Keywords Search(AC自动机)
  12. 深信服 adesk linux 客户端,Sangfor-aDesk巡检工具(深信服桌面云智能交付巡检助手)V2.1 正式版...
  13. 怎么用手机当电脑摄像头?安卓苹果都可以,巨简单的N种方案任君挑选
  14. 静默安装oracle11,Oracle11g静默安装
  15. 生意宝,淘宝,唯品会,58同城,去哪儿背后的赚钱生意经(转)
  16. vcf格式(vCard)转成excel的操作方法
  17. VMware ESXi 与ESX 产品之比较
  18. 金华职业技术学院计算机网络技术考试,金华职业技术学院2016年提前招生计算机应用技术专业测评方案...
  19. SIGIR'22 | 利用最小化互信息学习反事实推断中的解耦表征
  20. android runtime chrome,ARChon Runtime APK for Chrome_v2.1

热门文章

  1. RHEL5简单的引导故障解决
  2. javascript代码总结
  3. IT人不可不听的10个职场故事
  4. 206.12.15随笔--最近内心的一些想法
  5. java 注销变量_[ Java学习基础 ] Java对象的创建和销毁
  6. Javascript设计模式之发布-订阅模式
  7. BZOJ1016 [JSOI2008]最小生成树计数
  8. Python深入06 Python的内存管理
  9. 100-48微软(运算)
  10. C#颜色和名称样式对照表