数据结构:带哨兵位的单向链表和双向链表

链表有单向链表也有双向链表,先回忆一下单项链表

单向链表

上面是一个单向链表图,我们首先定义一个指针来存储第一个结构体指针的位置,来找到第一个节点,第一个节点中存储着一个值和下一个节点的地址,如果说一开始phead指向的指针为空,我们要在链表中添加值,我们首先去堆中申请一个结构体的空间,让phead中存储我们申请的结构体的地址,我们将添加值加到结构体中,让结构体指向NULL,这就是由空添加一个值的过程,phead一开始存储着NULL 的地址,变成了存储我们申请的结构体的地址,phead的前后值发生改变,第一种方法:我们要是传参时就用到了二级指针;第二种方法:用返回值去接收

带哨兵位的单项链表

上述单向链表中还要传二级指针有点麻烦,我们用一个哨兵位来解决,啥叫哨兵位,如图

我们在对phead进行初始化,就是申请一个头节点里面存放任意值,phead中存放的是头节点的地址,传参时传的是头节点的地址,当我们再添加值的时候直接让头结点的next指向我们添加的结构体即可,在这个过程中不需要我们去改变头节点的地址,改变的是头节点的结构体指向下一个的值,改变的结构体,所以不需要传二级指针

#pragma once
#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode {
	LTDataType x;
	struct ListNode* next;
}LTNode;
LTNode* ListInit();

先定义一个结构体,然后初始化函数,下面代码是初始化函数的实现

#include "HeadList.h"
LTNode* ListInit() {
	LTNode* phead = (LTNode*)malloc(sizeof(ListNode));
	phead->next = NULL;
	return phead;
}

接着来调用初始函数

#include"HeadList.h"
void Test1()
{
	LTNode* plist = ListInit();
}
int main(){
	Test1();
	return 0;
}

接着我们来写一个尾插函数,具体代码的区别就是,此时的接受实参的形参用的是一级指针

头文件

#pragma once
#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode {
	LTDataType data;
	struct ListNode* next;
}LTNode;
LTNode* ListInit();
void ListPushBack(ListNode* plist, LTDataType x);
void ListPrint(ListNode* plist);

尾插函数的实现,再加上一个打印函数了

void ListPushBack(ListNode* plist, LTDataType x) {
	ListNode* phead = plist;
	LTNode* cur = (LTNode*)malloc(sizeof(ListNode));
	cur->data = x;
	cur->next = NULL;
	assert(phead);
	while(phead->next!= NULL)
	{
		phead = phead->next;
	}
	phead->next = cur;
}
void ListPrint(ListNode* plist) {
	ListNode* phead = plist;
	assert(phead);
	while (phead != NULL)
	{
		phead = phead->next;
		printf("%d ", phead->data);
	}
}

这时候我们来测试

#include"HeadList.h"
void Test1()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 5);
	ListPushBack(plist, 3);
	ListPushBack(plist, 2);
	ListPrint(plist);
}
int main(){
	Test1();
	return 0;
}

打印结果如下

5 3 2

双向链表

双向链表一般都是带头来写,我们看一个双向链表的图示

双向链表和单向链表的区别:双向链表比单向链表多了一个prev,这个prev指向了上一个节点,而哨兵位的prev指向的是最后一个节点,这样就很容易就找到了尾节点了,尾节点的next指向哨兵位这样也很好的能找到头节点

我们先来定义一个结构体,这个结构体和原来的单向链表有所不同,里面多了一个prev参数,这个参数是来指向上一个节点的地址

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int ListDataType;
typedef struct ListNode {
	ListDataType data;
	ListNode* prev;
	ListNode* next;
}LTNode;

然后我们去实现初始化函数,我们在双向链表之前添加一个哨兵位,有了这个哨兵位之后就不用传递二级指针了

LTNode* ListInit();

下面是这个函数的实现

LTNode* ListInit() {
	LTNode* phead = (LTNode*)malloc(sizeof(ListNode));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

我们将phead返回给plist,plist的指针就是指向一个哨兵位的了

再接着实现打印函数和尾插函数

void ListPushBack(ListNode* phead, ListDataType x);
void ListPrint(ListNode* phead);

void ListPushBack(ListNode* phead, ListDataType x) {
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* cur = (LTNode*)malloc(sizeof(ListNode));
	cur->data = x;
	tail->next = cur;
	cur->prev = tail;
	cur->next = phead;
	phead->prev=cur ;
}
void ListPrint(ListNode* phead) {
	LTNode* tail = phead;
	while (tail->next != phead)
	{
		tail = tail->next;
		printf("%d ", tail->data);
	}
	printf("\n");
}

然后我们来测试代码

#include"List.h"
void Test1()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 5);
	ListPushBack(plist, 6);
	ListPushBack(plist, 3);
	ListPrint(plist);
}
int main()
{
	Test1();
	return 0;
}

测试结果如下:

 5 6 3

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值