[Data Structure and Algorithm] - Doubly Linked List

learning target:

  • Design and Implementation of Doubly Linked List

Learning Content:

1. The header file of the doubly linked list

#define _CRT_SECURE_NO_WARNINGS 1
//带头的双向循环链表
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int LTDataType;

typedef struct ListNode
{
    
    
	struct ListNode* next;//存下一个结点的地址
	struct ListNode* prev;//存上一个结点的地址
	LTDataType data;
}ListNode;

//初始化带头结点
//void ListInit(ListNode** pphead);
ListNode* ListInit();

//打印
void ListPrint(ListNode* phead);

//创建结点
ListNode* BuyListNode(LTDataType x);

//尾插 - 单链表尾插可以不找尾,定义一个尾指针
void ListPushBack(ListNode* phead, LTDataType x);

//尾删
void ListPopBack(ListNode* phead);

//头插
void ListPushFront(ListNode* phead, LTDataType x);

//头删
void ListPopFront(ListNode* phead);

//任意位置插入
void ListInsert(ListNode* pos, LTDataType x);

//任意位置删除
void ListErase(ListNode* pos);

//查找
ListNode* ListFind(ListNode* phead, LTDataType x);//返回找到的数的下标
//...
//int SeqListSort(SL* psl, SLDataType x);
//int SeqListBinaryFind(SL* psl, SLDataType x);

//空间销毁
//void ListDestory(ListNode* phead);
void ListDestory(ListNode** phead);

//数据清理 - 清理所有的数据节点,保留head头结点,可以继续使用
void ListClear(ListNode* phead);

#include <stdbool.h>
//判断链表是否为空 - 判断phead->next是否等于phead
bool ListEmpty(ListNode* phead);

//计算链表的长度
int ListSize(ListNode* phead);

Note:
The implementation structure of the doubly linked list is a doubly circular linked list with a head node with a sentinel bit.


2. Code implementation
1. ListNode* ListInit(); initialize the leading node
void ListInit(ListNode** pphead); initialize the leading node

//传值调用:不会改变外面的值
void ListInit(ListNode* phead)
{
    
    
	phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
}

Optimization method 1:

//传址调用
void ListInit(ListNode** pphead)
{
    
    
	//通过外面传的地址在函数里面,改变函数外面的变量的值
	*pphead = (ListNode*)malloc(sizeof(ListNode));//创建一个哨兵位头节点
	(*pphead)->next = *pphead;
	(*pphead)->prev = *pphead;
}

Optimization method 2:

//方法二:
//返回哨兵位结点的地址
ListNode* ListInit()
{
    
    
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));//创建一个哨兵位头节点
	if (phead == NULL)
		return NULL;
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2. ListNode* BuyListNode(LTDataType x); create a node

//创建结点
ListNode* BuyListNode(LTDataType x)
{
    
    
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if(node == NULL)
        return NULL;
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

3. void ListPrint(ListNode* phead); print
Start from the first node (non-sentry position), find the sentinel position node and stop printing.

void ListPrint(ListNode* phead)
{
    
    
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4. void ListPushBack(ListNode* phead, LTDataType x); Tail insertion
The tail insertion of the single-linked list does not need to find the tail, and defines a tail pointer.

void ListPushBack(ListNode* phead, LTDataType x)
{
    
    
	assert(phead);//链表为空,即哨兵结点开辟空间失败。一般不会失败,即一定哨兵位结点地址不为空,也不需要断言
	//找尾
	ListNode* tail = phead->prev;
	//插入新结点
	ListNode* newnode = BuyListNode(x);
	//链接:phead、tail、newnode
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
	//注意:(只有一个哨兵位结点)没有结点是否满足
	//注意:也不需要像不带头单链表一样,考虑没有结点的情况
}

Interpretation:
Because there is no need to change the passed sentinel position pointer, there is no need to pass the secondary pointer.
What is changed is the content in the sentinel bit structure, which can be modified directly using the sentinel bit pointer.


5. void ListPushFront(ListNode* phead, LTDataType x); head insertion
Insert after the head node, and save the address of the next node of the sentinel position.

void ListPushFront(ListNode* phead, LTDataType x)
{
    
    
	assert(phead);//assert(表达式);表达式为真,则什么都不干。表达式为假,则报错。
	//方法一
	ListNode* first = phead->next;
	ListNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//方法二:
	/*
	//顺序不能颠倒
	ListNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
	*/
	//注意:(只有一个哨兵位结点)没有结点是否满足
}

6. void ListPopFront(ListNode* phead);
delete head Delete after the head node, save the address of the next node of the first node.

void ListPopFront(ListNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);//判断链表为空时,继续删除的断言
	//方法一:
	/*
	ListNode* first = phead->next;
	phead->next = first->next;
	first->next->prev = phead;
	//注意释放位置
	free(first);
	*/

	//方法二:
	ListNode* first = phead->next;
	ListNode* second = first->next;
	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;//无需此行
	//注意:(只有一个哨兵位结点)没有结点是否满足
}

7. void ListPopBack(ListNode* phead); Tail delete
Save the address of the previous node of the node to be deleted.

void ListPopBack(ListNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);//判断链表只有哨兵位结点(为空)时,继续删除的断言
	//方法一:
	ListNode* tail = phead->prev;
	ListNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	tail = NULL;//可以不置空,因为tail为局部变量,出了局部范围自动释放

	//方法二:
	/*
	ListNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next = phead;
	//注意释放位置
	free(tail);
	*/
	//注意:(只有一个哨兵位结点)没有结点是否满足
}

8. void ListInsert(ListNode* pos, LTDataType x); insert at any position
Insert x in front of the pos node, and save the address of the previous node of pos.

void ListInsert(ListNode* pos, LTDataType x)//注意:不需要传链表
{
    
    
	assert(pos);
	//如果需要在空链表插入,需要结合phead指针。
	ListNode* posPrev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	//链接:posPrev、newnode、pos
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;

	//方法二:
	/*
	//注意链接顺序
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
	*/
	//注意:pos在哨兵位结点,相当于尾插
	//注意:pos在第一个结点,相当于头插

	//注意:(只有一个哨兵位结点)没有结点是否满足
	//因为phead->prev==phead;phead->next==phead;对于头插(phead->next),尾插(phead)的结点都不为空,可以满足要求assert(pos);
}

Interpretation:
1. pos is at the sentinel node, which is equivalent to tail plugging
2. pos is at the first node, which is equivalent to head plugging

//优化:尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
    
    
	assert(phead);
	//尾插:在pos为phead的前面插入x
	ListInsert(phead, x);
}
//优化:头插 - 在head结点后插入
void ListPushFront(ListNode* phead, LTDataType x)
{
    
    
	assert(phead);
	//头插:在pos为phead->next的前面插入x
	ListInsert(phead->next, x);
}

9. void ListErase(ListNode* pos); delete at any position Delete
the value at position pos, save the address of the node before and after pos.

void ListErase(ListNode* pos)
{
    
    //不能对空链表删除,也就是不能删除哨兵位结点。
	assert(pos);
	//方法一:
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;
	free(pos);
	posPrev->next = posNext;
	posNext->prev = posPrev;

	//方法二:
	/*
	//注意链接顺序
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	*/
	//注意:pos在哨兵位的前一个结点,相当于尾删
    //注意:pos在第一个结点,相当于头删

	//注意:(只有一个哨兵位结点)没有结点是否满足
	//因为phead->prev==phead;phead->next==phead;对于头删(phead->next),尾删(phead->prev)的结点都不为空,可以满足要求assert(pos);
	//但不可以删除。
}

Interpretation:
1. pos is at the previous node of the sentinel position, which is equivalent to tail deletion
2. pos is at the first node, which is equivalent to head deletion

//优化:尾删
void ListPopBack(ListNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);//判断链表只有哨兵位结点(为空)时,继续删除的断言。或者assert(phead->prev != phead);
	ListErase(phead->prev);
}
//优化:头删 - 在head结点后删除
void ListPopFront(ListNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);//判断链表为空时,继续删除的断言
	ListErase(phead->next);
}

10. ListNode* ListFind(ListNode* phead, LTDataType x); Search, return the subscript of the found number
Return the address of the node corresponding to the found number, which can also be used as the basis of the modification function.

ListNode* ListFind(ListNode* phead, LTDataType x)
{
    
    
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	//走到这里没有找到
	return NULL;
}

11. void ListDestory(ListNode* phead); space destruction, after destroying the created linked list to release
the node, the value inside is a random value, and the next node cannot be found: save the next node of the current node first, and release the current node node, iterate again.

void ListDestory(ListNode* phead)
{
    
    
	assert(phead);
	//第一种方法:
	ListNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		ListNode* next = cur->next;
		free(cur);
		cur = next;//迭代
	}
	//来到这里,1、如果链表不使用,则删除head,2、如果链表继续使用,则不删除head
	//假设如果链表继续使用,则不删除head
	phead->next = phead;
	phead->prev = phead;

	//假设如果链表不使用,则删除head
	free(phead);
	phead = NULL;//本身phead需要置空,通过调试发现函数里面置空了,但是函数外面没有作用,因为head和phead它们是两个独立的空间
    
    //第二种方法:
    //1、如果链表不使用,则删除head
	ListClear(phead);
	free(phead);//释放空间,让其回归内存,即变量的地址仍不变,但是地址的值为随机值,空间还给了操作系统
	phead = NULL;//本身phead需要置空,通过调试发现函数里面置空了,但是函数外面没有作用,因为head和phead它们是两个独立的空间
    
	//解决方法:
	//1、在外面主函数最末尾补一行phead = NULL;(不建议)
	//2、二级指针:即传址调用,函数里面想要改变函数外面的值。传值调用:函数里面不改变函数外面的值。
	//3、使用返回值,外面用同一个变量接收返回值
}

optimization:

//第三种方法:传二级指针
void ListDestory(ListNode** pphead)
{
    
    
    //1、如果链表不使用,则删除head
	assert(*pphead);
	ListClear(*pphead);
	free(*pphead);//释放空间,让其回归内存,即变量的地址仍不变,但是地址的值为随机值,空间还给了操作系统
	//2、二级指针
	*pphead = NULL;
}

12. void ListClear(ListNode* phead); data cleaning
Clean up all data nodes, keep the head node, you can continue to use it. Example: shopping cart business

void ListClear(ListNode* phead)
{
    
    
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//来到这里,1、如果链表不使用,则删除head,2、如果链表继续使用,则不删除head
	//释放掉所有数据结点,让头结点head保持带头双向循环链表结构
	phead->next = phead;
	phead->prev = phead;
}

3. Code testing

#define _CRT_SECURE_NO_WARNINGS 1
//带头 双向 循环 链表
#include "List.h"
void TestList1()
{
    
    
    //方法一:
	/*ListNode* head = NULL;
	ListInit(&head);*/

	//方法二:
	ListNode* head = NULL;
	head = ListInit();

    //方法三:直接在外面初始化
	/*ListNode* head = NULL;
	head->next = head;
	head->prev = head;*/

	ListPushBack(head, 1);
	ListPushBack(head, 2);//传入的是ListNode*类型,形参接受也用ListNode*
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);

	ListPopBack(head);
	ListPopBack(head);
	ListPopBack(head);
	ListPrint(head);

	ListPushFront(head, -1);
	ListPushFront(head, -2);
	ListPushFront(head, -3);
	ListPrint(head);

	ListPopFront(head);
	ListPopFront(head);
	ListPrint(head);

	//销毁空间
	ListDestory(&head);
}
void TestList2()
{
    
    
	ListNode* head = NULL;
	head = ListInit();
	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPrint(head);

    //查找,并修改
	ListNode* pos = ListFind(head, 3);
	if(pos)
    {
    
    
        pos->data *= 10;
    }
    ListPrint(head);

    //在pos的前面插入30
    ListInsert(pos, 30);
	ListPrint(head);

	//删除4
	pos = ListFind(head, 4);
	if (pos)
	{
    
    
		ListErase(pos);
	}
	ListPrint(head);

	//将1改为10
	pos = ListFind(head, 1);
	if (pos)
	{
    
    
		pos->data = 10;
	}
	ListPrint(head);

	//销毁空间
	//ListDestory(head);
	ListDestory(&head);

}
 int main()
{
    
    
	//TestList1();
	TestList2();
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_48163964/article/details/130133109