链表有单向链表也有双向链表,先回忆一下单项链表
单向链表
上面是一个单向链表图,我们首先定义一个指针来存储第一个结构体指针的位置,来找到第一个节点,第一个节点中存储着一个值和下一个节点的地址,如果说一开始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