数据结构之单链表与双链表

一、单链表中的循环链表:

目前我们的链表最后一个结点的pNext指向的是NULL;循环链表中的最后一个结点的pNext指向的是头结点;

循环链表和不是循环链表的区别:判断条件不一样!

p->pNext == NULL;

p->pNext == pHeader;

二、单链表和数组的区别:

单链表的优势:

1、可以解决数组大小一旦定义之后很难改变的缺点;

2、链表中的各个结点之间可以不用连续分配,分配的是堆空间!可以充分利用碎片化内存!回忆一下malloc的分配原理!

单链表的缺点:

1、每个结点要分配四字节的指针变量用来保存下一个结点的地址,浪费了内存空间;无论是什么类型的指针变量,所占用的字节大小都是4个字节;

2、因为单链表里面只能指向下一个结点,不能够指向上一个结点,导致的结果就是一旦错过了某个结点,还得重新遍历!

三、双链表

1、引入:为了解决单链表的第2个缺点;

模型:

在单链表的基础上每个结点加上了指向前一个节点的前向指针!(对于我们这里的带头结点的情况,第一个有效结点的前向指针指向的是头结点,头结点的前向指针指向的

是NULL);

比较单链表和双链表的结点的差别:

①单链表的结点:

struct node
{

int data; //保存的本身的数据;    数据域; 

struct node *pNext; //结构体类型的指针,保存下一个结构体的地址;    指针域;

};

②双链表的结点:

struct node
{

int data; //保存的本身的数据;    数据域; 

struct node *pNext; //结构体类型的指针,保存下一个结构体的地址;    指针域;

struct node *pPrev; //结构体类型的指针,保存上一个结构体的地址;    指针域;
};

③双链表:

不是两个链表,其实就是比单链表多了一个指向前一个结点的指针。

单链表理解为单方向行驶的车道,双链表理解为双向行驶的车道。

其实就是比单链表多了一种遍历方式,前向遍历的方式。

2、具体操作:

①创建结点:

struct node *create_node(int num)
{

struct node *p = (struct node *)malloc(sizeof(struct node)); //注意malloc的返回值;

if( NULL == p)
{

printf("malloc error!\n");

return NULL;
}

bzero(p,sizeof(struct node));

p->pNext = NULL;

p->pPrev = NULL; //多了这一行代码;

return p;

}

②插入:
尾插:

第一步:找到链表的最后一个结点;

第二步:将新的结点和原来的最后一个结点连接起来;

(1)原来的尾节点的pNext指针指向新结点的首地址;

(2)新结点的pPrev指针指向原来的尾节点的首地址;

(3)新结点的pNext指针指向NULL; //在Create_node执行过了,可以跳过

void insert_tail(struct node *pHeader,struct node *new)
{

struct node *p = pHeader;

//找到链表的尾节点;

while(p->pNext != NULL)
{
p = p->pNext;

}

//将新的结点和原来的最后一个结点连接起来

p->pNext = new;

new->pPrev = p; //多了前向指针的指向问题!

}

头插:

1、待插入的新结点的pNext指向原来的第一个结点;

new->pNext = pHeader->pNext;

2、 原来的第一个结点的前一个节点此时变成了带插入的新的结点,将原来第一个结点的pPrev指向待插入的结点;

pHeader->pNext->pPrev = new;

3、 头结点的pNext指向新插入的结点;

pHeader->pNext = new;

4、新插入的结点的pPrev指向头结点;

new->pPrev = pHeader;

遍历:

正向遍历:

正向遍历和单链表的遍历是一样的,做稍微的修改。

struct node * display_link(struct node *pHeader)
{
struct node *p = pHeader;

while(p->pNext != NULL)
{

p = p->pNext; //跳过头结点;

printf("%d\n",p->data);
}
return p;
}

前向遍历:

从双链表的尾节点开始往前面开始遍历。

1、获取链表中最后一个尾节点的地址;(关键一步)

2、用while循环依次p->pPrev != NULL 

中间插入:

int insert_mid(struct node *pHeader,int num,struct node *new)
{
struct node *p = pHeader;

while(p->pNext != NULL)
{
p = p->pNext;

if(p->data == num)
{
//已经是尾节点!

if(p->pNext == NULL)
{
p->pNext = new;

new->pPrev = p;

}
else
{
new->pNext = p->pNext;

p->pNext->pPrev = new; //如果不单独考虑尾节点的情况,在这儿会有段错误,毕竟p->pNext = NULL;

p->pNext = new;

new->pPrev = p;

}
return 0;
}
}
}

删除:

链表:头结点-->Node1-->Node2-->Node3-->NULL;

删除:Node3;

伪代码:

1、遍历链表到Node2这个结点;

2、明确指针的指向关系:

(1)将Node2的前面一个结点(即Node1)的pNext指向Node2的后面一个结点;

(2)将Node2的后面一个结点(即Node3)的pPrev指向Node2的前面一个结点(即Node1);

(3)释放Node2这个结点所分配的堆空间;

int delete_node(struct node *pHeader,int num)
{
struct node *p = pHeader;

while(p->pNext != NULL)

{
p = p->pNext; //跳过头结点;

if(p->data == num)
{
if(p->pNext == NULL) //判断尾节点;
{
p->pPrev->pNext = NULL;

}
else
{
p->pPrev->pNext = p->pNext;
p->pNext->pPrev = p->pPrev;
}

free(p);

p = NULL;

return 0;
}
}
printf("delete error!\n");
}

总结:

以上就是带头结点的单链表和双链表的基本操作。

插入(头插、尾插、中间插入)、删除、逆序【双链表不需要逆序】、遍历;

学习方法:

1、可以画图想出逻辑上的算法;

2、根据逻辑算法写出伪代码;

3、编码;

注意的是:是否跳过了头结点,对于尾节点要特殊考虑一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值