27 顺序表 · 链表

news2024/12/22 18:01:47

目录

一、单链表

(一)概念

1、节点

2、链表的性质

(二)单链表的实现

(三)单链表算法题

1、移除链表元素

2、反转链表

3、链表的中间节点

4、合并两个有序的单链表

5、链表分割

6、链表的回文结构

7、相交链表

8、环形链表 · 是否是环形链表

9、环形链表 · 寻找环形起始点

10、随机链表的复制

二、链表的分类

三、双向链表

(一)概念与结构

(二)双向链表的实现

四、顺序表与链表的分析


一、单链表

(一)概念

        概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

        总结:链表的逻辑结构是线性的,数据是一个接着一个的;物理结构不一定是线性的。

1、节点

        与顺序表不同的是,链表里的每个元素都是独立申请下来的空间,我们称之为“结点”。

        单链表的结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量),也称为数据域和指针域。

        在单链表中需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

2、链表的性质

        ① 链式结构在逻辑上是连续的,在物理结构上不一定连续。

        ② 节点一般是从堆上申请的。(通过动态内存管理进行开辟空间)

        ③ 从堆上开辟的空间,是按照一定策略分配出来的,每次开辟的空间可能连续,也可能不连续。

(二)单链表的实现

        单链表常用名:SingleList,简称SLT;单链表节点常用名字:SLTNode。

        定义链表结构体,其实是在定义节点的结构体,因为链表是由一个个的节点组合起来的。

单链表的编写:

① 头文件:定义单链表的节点结构体,声明要提供的操作(起到目录作用);

② cpp文件:编写具体实现各种操作(起到内容作用);

③ 测试文件:测试是否可以正常运行。

        SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<iostream>
using namespace std;

typedef int SLTDataType;
typedef struct SLTNode
{
	SLTDataType data;
	struct SLTNode* next;
}SLTNode;

//一、创建新节点、打印链表、查找节点
//	(一)创建新节点
SLTNode* create_node(SLTDataType num);
//	(二)打印链表
void SLTPrint(SLTNode* phead);
//	(三)查找节点
SLTNode* find_node(SLTNode* phead, SLTDataType num);

//二、插入数据(传过来的单链表可以为空,单一定要创建新节点)
//	(一)尾插
void SLTPushBack(SLTNode*& phead, SLTDataType num);
//	(二)头插
void SLTPushFront(SLTNode*& phead, SLTDataType num);
//	(三)指定位置插
//		1、指定位置之前插
void SLTInsert(SLTNode*& phead, SLTNode* pos, SLTDataType num);
//		2、指定位置之后插
void SLTInsertAfter(SLTNode* pos, SLTDataType num);

//三、删除数据(要判断传过来的节点是否为空)
//	(一)尾删
void SLTPopBack(SLTNode*& phead);
//	(二)头删
void SLTPopFront(SLTNode*& phead);
//	(三)指定位置删除
//		1、指定位置删除
void SLTErase(SLTNode*& phead, SLTNode* pos);
//		2、指定位置之后删除
void SLTEraseAfter(SLTNode* pos);
//	(四)销毁单链表
void SLTDestroy(SLTNode*& phead);

        SList.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

//一、创建新节点、打印链表、查找节点
//	(一)创建新节点
SLTNode* create_node(SLTDataType num)
{
	SLTNode* new_node = (SLTNode*)malloc(sizeof(SLTNode));
	if (new_node == nullptr)
	{
		perror("creat_node fail");
		exit(1);
	}
	new_node->data = num;
	new_node->next = nullptr;
}
//	(二)打印链表
void SLTPrint(SLTNode* phead)
{

	while (phead)
	{
		cout << phead->data << " -> ";
		phead = phead->next;
	}
	cout << "nullptr" << endl;
}
//	(三)查找节点
SLTNode* find_node(SLTNode* phead, SLTDataType num)
{
	while (phead)
	{
		if (phead->data == num)
			return phead;
		phead = phead->next;
	}
}

//二、插入数据(传过来的单链表可以为空,单一定要创建新节点)
//	(一)尾插
void SLTPushBack(SLTNode*& phead, SLTDataType num)
{
	SLTNode* new_node = create_node(num);
	if (phead == nullptr)
		phead = new_node;
	else
	{
		SLTNode* node = phead;
		while (node->next)
			node = node->next;
		node->next = new_node;
		new_node->next = nullptr;
	}
}
//	(二)头插
void SLTPushFront(SLTNode*& phead, SLTDataType num)
{
	SLTNode* new_node = create_node(num);
	if (phead == nullptr)
		phead = new_node;
	else 
	{
		new_node->next = phead;
		phead = new_node;
	}
}
//	(三)指定位置插
//		1、指定位置之前插
void SLTInsert(SLTNode*& phead, SLTNode* pos, SLTDataType num)
{
	assert(pos);
	SLTNode* new_node = create_node(num);
	if (phead == pos)
	{
		new_node->next = phead;
		phead = new_node;
	}
	else
	{
		SLTNode* pre_node = phead;
		while (pre_node)
		{
			if (pre_node->next == pos)
			{
				new_node->next = pre_node->next;
				pre_node->next = new_node;
				break;
			}
			pre_node = pre_node->next;
		}
	}
}
//		2、指定位置之后插
void SLTInsertAfter(SLTNode* pos, SLTDataType num)
{
	assert(pos);

	SLTNode* new_node = create_node(num);

	new_node->next = pos->next;
	pos->next = new_node;
}

//三、删除数据(要判断传过来的节点是否为空)
//	(一)尾删
void SLTPopBack(SLTNode*& phead)
{
	assert(phead);

    if(phead->next == nullptr)
    {
        free(phead);
        phead == nullptr;
    }
    else
    {
        SLTNode* pre_node = phead;
	    while (pre_node->next->next)
		    pre_node = pre_node->next;
	    SLTNode* node = pre_node->next;
	    pre_node->next = nullptr;
	    free(node);
	    node = nullptr;
    }
}
//	(二)头删
void SLTPopFront(SLTNode*& phead)//若不传引用,则会造成二次free
{
	assert(phead);

	SLTNode* node = phead->next;
	free(phead);
	phead = node;
}
//	(三)指定位置删除
//		1、指定位置删除
void SLTErase(SLTNode*& phead, SLTNode* pos)
{
	assert(phead && pos);

	if (phead == pos)
	{
		phead = phead->next;
		free(pos);
		pos = nullptr;
	}
	else
	{
		SLTNode* pre_node = phead;
		while (pre_node)
		{
			if (pre_node->next == pos)
			{
				pre_node->next = pos->next;
				free(pos);
				pos = nullptr;
				break;
			}
			pre_node = pre_node->next;
		}
	}
}
//		2、指定位置之后删除
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	SLTNode* node = pos->next;
	pos->next = node->next;
	free(node);
	node = nullptr;
}
//	(四)销毁单链表
void SLTDestroy(SLTNode*& phead)
{
	assert(phead);
	SLTNode* next_noed = phead->next;
	while (next_noed)
	{
		free(phead);
		phead = next_noed;
		next_noed = next_noed->next;
	}
	free(phead);
	phead = nullptr;
}

        test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

void test()
{
	//SLTNode* re =  create_node(15);
	SLTNode* plist = nullptr;

	for (int i = 1; i < 11; i++)
	{
		SLTPushBack(plist, i);
		//SLTPushFront(plist, i);
	}
	SLTPrint(plist);

	SLTNode* re = find_node(plist, 5);

	//SLTInsert(plist, re, 255);
	//SLTInsertAfter(re, 255);

	//for (int i = 0; i < 1; i++)
	//{
	//	//SLTPopBack(plist);
	//	//SLTPopFront(plist);
	//}
	//SLTErase(plist, re);
	//SLTEraseAfter(re);
	SLTDestroy(plist);
	SLTPrint(plist);
}

int main()
{
	test();
	return 0;
}

        总结:

        ① 单链表没办法从右向左遍历,只能从左向右遍历,若是对单链表进行操作,需要影响到目标节点的前一个节点时,需要传递头节点进行遍历,找到目标节点的前一个节点进行修改。

        ② 在链表中,没有增容的概念(不使用realloc),需要插入新的数据,直接申请一个节点大小的空间就可以了。

        ③ 判断传参传的是一级指针还是二级指针(使用 “引用” ),就要看是否改变了实参地址指向的空间(这个改变是指实参所指向的空间不会变成另一个地址的空间)。

        比如:

        在尾插中存在两种情况,第一种是存在节点的时候,直接进行尾插,不需要改变传过来的头节点的指针指向;而第二种情况就是头指针指向空的时候,需要把头指针指向空地址改成指向新开辟的空间的地址,这样就改变了实参指向的地址空间,所以在尾插操作中需要传二级指针(使用 “引用” )。

        而在删除指定节点之后的节点的操作中,并没有改变对指定节点的空间进行改变,所以传一级指针即可。

(三)单链表算法题

1、移除链表元素

        题目链接如下:

                https://leetcode.cn/problems/remove-linked-list-elements/description/

        解题思路:

                创建新链表,然后遍历原链表,把除了【指定移除节点】的其他节点全部复制尾插在新链表后面。

                注意点:需要把新链表最后的节点的next置为空,因为可能会链接着需要删除的节点的地址。

        代码如下:

typedef struct ListNode SLTNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{
    //创建新链表
    SLTNode* new_head, * new_tail;
    new_head = new_tail = NULL;

    //遍历原链表
    SLTNode* pcur = head;
    while(pcur)
    {
        if(pcur->val != val)
        {
            if(new_head == NULL)//尾插分为两种情况:①链表为空 ②链表不为空
                new_head = new_tail = pcur;
            else
            {
                new_tail->next = pcur;
                new_tail = new_tail->next;
            }
        }
        pcur = pcur->next;
    }
    if(new_tail)
        new_tail->next = NULL;
    return new_head;
}

2、反转链表

        题目链接如下:

                https://leetcode.cn/problems/reverse-linked-list/description/

        题解思路:

                创建三个指针,第一个指针 p1 指向 nullptr,第二个指针指向头节点,第三个指针指向头节点的下一个节点。然后把第二个指针的指向改成第一个指针所指向的 nullptr,再把第二个指针的地址赋给第一个指针,第三个指针的地址赋给第二个指针,第三个指针向后走一步。

        注意点:

                需要判断第三个指针是否为空:因为在处理最后一个节点的指向的时候,第三个指针的指向已经是空了,所以要判断一下第三个指针是否为空,若为空就不能解引用结构体里面的值把next赋给自己了;

                循环的判断条件为第二个指针是否为空,因为当第二个指针为空的时候,就没有需要改变指向的节点了(画图理解)。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
        return head;
    else
    {
        ListNode* p1, *p2, *p3;
        p1 = NULL, p2 = head, p3 = head->next;
        while(p2)
        {
            p2->next = p1;
            p1 = p2;
            p2 = p3;
            if(p3)
                p3 = p3->next;
        }
        return p1;//p1为反转链表的头指针
    }
}

3、链表的中间节点

        题目链接如下:

                https://leetcode.cn/problems/middle-of-the-linked-list/description/

        解题思路:

                创建两个指针,起始位置都指向目标链表的第一个节点处;

                一个指针为慢指针,另一个指针为快指针,顾名思义慢指针的移动速度比快指针慢,慢指针走一步,快指针走两步;

                当快指针到达最后一个节点指向的空地址,或者快指针的下一个节点就是空地址的时候,慢指针所指向的节点就是中间节点。

                原理:假设快指针走的路程为n,快指针走的路程是慢指针的两倍,即 2*慢=快;此时慢指针走的路程就是n/2。当链表的长度为5的时候,快指针到达了终点5,而慢指针的路程为2(整形去除小数点),又因为起始位置为1,所以到达的节点是3,为中间节点;当链表的长度为6的时候,快指针到达了终点7,慢指针的路程为3,又因为起始位置为1,所以到达的节点是4,为中间节点。

        注意点:

                while判断的 pfast && pfast->next 的顺序不能换,若换过来判断偶数节点数中间节点的时候,pfast已经为空地址,空地址不能解引用 pfast->next,这样会报错。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* pfast, *pslow;
    pfast = pslow = head;
    while(pfast && pfast->next)
    {
        pfast = pfast->next->next;
        pslow = pslow->next;
    }
    return pslow;
}

4、合并两个有序的单链表

        题目链接如下:

                https://leetcode.cn/problems/merge-two-sorted-lists/description/

        解题思路如下:

                创建一个新的链表,创建两个指针分别指向两个有序的单链表;两个有序单链表的节点开始比较,较小或者相等的节点尾插到新链表中,然后比较时较小链表的指针与新链表中指向为节点的指针都向后走一步,开始循环比较大小;循环结束后若是有一方还存在数据,直接把数据全部插入到新链表中。

        注意点:

                因为每次比较之后都要判断新链表的头尾指针是否为空(为尾插分两种情况),显得代码冗余,可以直接动态申请一个内存给新链表的头尾指针,这样就可以不用检查判断是否为空了。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1 == NULL)
        return list2;//这里处理了list1为空的情况与list1和list2都为空的情况
    if(list2 == NULL)
        return list1;

    ListNode* new_head , *new_tail;//创建新链表
    new_head = new_tail = (ListNode*)malloc(sizeof(ListNode));

    ListNode* p1 = list1, *p2 = list2;//创建指针指向两个数组

    while(p1 && p2)
    {
        if(p1->val < p2->val)
        {
            new_tail->next = p1;
            new_tail = p1;
            p1 = p1->next;
        }
        else
        {
            new_tail->next = p2;
            new_tail = p2;
            p2 = p2->next;
        }
    }
    if(p1)
        new_tail->next = p1;
    if(p2)
        new_tail->next = p2;

    ListNode* node = new_head->next;//释放掉无有效数据的哨兵节点
    free(new_head);
    new_head = node;

    return new_head;
}

5、链表分割

        题目链接如下:

                https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70

        解题思路:

                创建两个非空链表,每个链表都要创建哨兵节点,这样不用每次尾插都要检查节点指针是否为空(因为单链表的尾插有两种情况);然后进行比较数据大小,把小于x的属于与大于或等于x的数据分别插入小链表与大链表;最后把大链表的节点插入小链表中,释放哨兵节点。

        注意点:

                尾插不会改变要插入节点的next,这里原来的节点的next可能还是下一个较小节点的地址,这样打印会造成死循环;大连表尾插进小链表后需要把大链表的最后一个节点的next置空。

        答案代码如下:

class Partition 
{
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
        //创建两个非空链表(创建哨兵节点,这样不用每次尾插都要检查节点指针是否为空)
        ListNode* less_head, * less_tail;
        less_head = less_tail = (ListNode*)malloc(sizeof(ListNode));

        ListNode* big_head, * big_tail;
        big_head = big_tail = (ListNode*)malloc(sizeof(ListNode));

        //比较数据大小,分别插入大链表与小链表
        ListNode* pcur = pHead;
        while(pcur)
        {
            if(pcur->val < x)
            {
                less_tail->next = pcur;
                less_tail = less_tail->next;
            }
            else 
            {
                big_tail->next = pcur;
                big_tail = big_tail->next;
            }
            pcur = pcur->next;
        }

        //把大链表的节点插入小链表中
        //尾插不会改变要插入节点的next
        //这里原来的节点的next可能还是下一个较小节点的地址,这样打印会造成死循环,需要把大链表的最后一个节点的next置空
        big_tail->next = nullptr;
        less_tail->next = big_head->next;

        //释放哨兵节点
        ListNode* re = less_head->next;
        free(less_head);
        free(big_head);
        less_head = big_head = nullptr;
        return re;
    }
};

6、链表的回文结构

        题目链接如下:

                https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

        解题思路:

                回文数字、字符串:轴对称;

                需要用到的思想为前面题目中的找中间节点与反转链表:设置快慢指针,找到中间节点;设置三个指针,分别指向nullptr、中间节点与中间节点的第二个指针,进行指向反转。最后把反转后的链表与中间节点之前的部分链表从头开始比较,若完全相等就是回文结构。

        答案代码如下:

class PalindromeList {
public:
    ListNode* fine_mid(ListNode* phead)
    {
        ListNode* pslow, * pfast;
        pslow = pfast = phead;
        while(pfast && pfast->next)
        {
            pslow = pslow->next;
            pfast = pfast->next->next;
        }
        return pslow;
    }

    ListNode* reversal(ListNode* mid)
    {
        ListNode* p1, *p2, *p3;
        p1 = nullptr, p2 = mid, p3 = mid->next;
        while(p2)
        {
            p2->next = p1;
            p1 = p2;
            p2 = p3;
            if(p3)
                p3 = p3->next;
        }
        return p1;
    }

    bool chkPalindrome(ListNode* A) 
    {
        //一、找中间节点
        ListNode* mid_node = fine_mid(A);
        //二、把中间节点后面的数据都反转
        ListNode* rever_head = reversal(mid_node);
        //三、把反转之后的节点与原头节点
        while(rever_head)//循环条件是rever_head是否存在,因为rever_head指向的单链表的最后有nullptr,而A取不符合条件
        {
            if(rever_head->val != A->val)
                return false;

            rever_head = rever_head->next;
            A = A->next;
        }
        return true;
    }
};

7、相交链表

        题目链接如下:

                https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

        相交链表的图例如下:

        解题思路:

                首先从两个不同的头节点开始到尾进行遍历,并计算从不同头节点开始的总节点个数,并计算节点个数之差的绝对值;然后设置长链表与短链表,让长链表先走节点差数的步数,再开始分别遍历两个链表,若有相同地址的节点,则返回该节点的地址,若没有相同地址的节点,则返回nullptr。

        注意点:

                求绝对值函数:abs()。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    //遍历链表,找节点差
    ListNode* node_A = headA;
    ListNode* node_B = headB;
    int size_A = 0, size_B = 0;
    while(node_A)
    {
        size_A++;
        node_A = node_A->next;
    }
    while(node_B)
    {
        size_B++;
        node_B = node_B->next;
    }
    int gap = abs(size_A - size_B);//计算节点个数差

    //设置长短链表
    ListNode* long_list = headA;
    ListNode* short_list = headB;
    if(size_A < size_B)
    {
        long_list = headB;
        short_list = headA;
    }
    while(gap--)//先让长链表走节点差步
        long_list = long_list->next;

    //遍历两链表,有相同地址值就返回节点地址,没有就返回null
    while(long_list && short_list)
    {
        if(long_list == short_list)
            return long_list;
        long_list = long_list->next;
        short_list = short_list->next;
    }
    return NULL;
}

8、环形链表 · 是否是环形链表

        题目链接如下:

                https://leetcode.cn/problems/linked-list-cycle/description/

        解题思路如下:

                带环链表:从头节点开始遍历链表,遍历不会结束。链表的尾节点next指针不指向空;

                使用的是快慢指针的思想(即慢指针一次走一步,快指针⼀次走两步),两个指针从链表起始位置开始运行,如果链表带环则⼀定会在环中相遇,否则快指针率先走到链表的未尾。

        注意点:

                无论快指针一次走多少步,最终都会与慢指针相遇。并且快指针走了三步及以上步数,就会引入额外的代码步骤,所以一般快指针都是走两步。

        答案代码如下:

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
    ListNode* fast = head, *slow = head;
    while(fast && fast->next)
    {
        if(fast == slow)
            return true;
        fast = fast->next->next;
        slow = slow->next;
    } 
    return false;
}

9、环形链表 · 寻找环形起始点

        题目链接如下:

                https://leetcode.cn/problems/linked-list-cycle-ii/description/

        解题思路:

                解题原理:让⼀个指针从链表起始位置开始遍历链表,同时让⼀个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。

                具体过程:设置快慢指针从环形链表的头节点开始遍历,慢指针走一步,快指针走两步,得到他们的相遇点并记录下来;然后设置两个速度相同的指针,一个从环形链表的头节点开始遍历,另一个从快慢指针的相遇点开始遍历,他们每次都是走一步;最终会在环形链表的循环起始点相遇。

        答案代码如下:

typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) 
{
    ListNode* fast = head;
    ListNode* slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            ListNode* node = head;
            while(node != slow)
            {
                node = node->next;
                slow = slow->next;
            }
            return slow;
        }
    }
    return NULL;
}

10、随机链表的复制

        题目链接如下:

                https://leetcode.cn/problems/copy-list-with-random-pointer/description/

        解题思路:

                原链表如下:

                ① 复制旧链表中的val值来创建新节点,插入到旧链表头节点之后的每个节点之间:

                ② 根据旧链表的 random 指针的关系来赋值给新节点的 random 指针:

                node->random = pcur->random->next; 若pcur->random指向空,直接让pcur->random指向空。

                ③ 断开新旧节点之间的联系。

        答案代码如下:

typedef struct Node node;

node* Buy_node(int num)
{
    node* re = (node*)malloc(sizeof(node));
    re->val = num;
    re->next = re->random = NULL;

    return re;
}

void Add_node(node* phead)
{
    node* phead_after = NULL;
    node* new_node = NULL;
    while(phead)
    {
        new_node = Buy_node(phead->val);
        phead_after = phead->next; 

        new_node->next = phead_after;
        phead->next = new_node;
        phead = phead_after;    
    }
}

struct Node* copyRandomList(struct Node* head) 
{
    //处理传空指针的情况
    if(head == NULL)
        return NULL;

	//一、创建节点,并尾插在每个原节点的后面
    Add_node(head);

    //二、处理新节点的random指针
    node* pcur = head;
    node* copy = pcur->next;
    while(pcur->next->next)//这里条件结束后不能处理最后一个节点
    {
        if(pcur->random != NULL)
            copy->random = pcur->random->next;
        pcur = copy->next;
        copy = pcur->next;
    }
    if(pcur->random != NULL)
            copy->random = pcur->random->next;

    //三、断开新链表与旧链表的联系
    pcur = head;
    node* new_head = pcur->next, * new_tail = pcur->next;
    while(pcur->next->next)
    {
        pcur = pcur->next->next;
        new_tail->next = pcur->next;
        new_tail = new_tail->next;
    }
    return new_head;
}

二、链表的分类

        链表的结构非常多样,以下情况组合起来就有八种链表结构:

        每种情况具体如下:

        

        虽然有这么多的链表结构,但实际中最常用的还是两种结构:单链表双向链表

        单链表的全称是:单向不带头不循环链表;

        双向链表的全称是:双向带头循环链表。

        双向链表与单链表完全不是同一类。

三、双向链表

(一)概念与结构

        带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”。

        双向链表的节点结构:数据 + 指向后一个节点的指针 + 指向前一个节点的指针。

        双向链表比单链表多的东西:哨兵节点 + 指向前一个节点的指针 + 双向循环。

(二)双向链表的实现

双向链表的编写:

① 头文件:定义双向链表的节点结构体,声明要提供的操作(起到目录作用);

② cpp文件:编写具体实现各种操作(起到内容作用);

③ 测试文件:测试是否可以正常运行。

List.h

#pragma once

#include<stdlib.h>
#include<iostream>
#include<stdbool.h>
#include<assert.h>
using namespace std;

typedef int LTDataType;
typedef struct LTNode
{
	LTDataType data;
	struct LTNode* prev;
	struct LTNode* next;

}LTNode;

//一、节点的创建并初始化、打印链表、判断头节点、查找节点、销毁链表
//(一)节点的创建并初始化
LTNode* Buy_node(LTDataType num);
//(二)链表的初始化
LTNode* LTInit();
//(三)打印链表
void LTPrint(LTNode* phead);
//(四)判断是否只有头节点
bool LTEmpty(LTNode* phead);
//(五)查找节点
LTNode* LTFine(LTNode* phead, LTDataType num);
//(六)销毁链表(是指整个链表都销毁,包括哨兵尾,会影响头指针)
void LTDestroy(LTNode*& phead);

//二、节点的插入
//(一)尾插
void LTPushBack(LTNode* phead, LTDataType num);
//(二)头插
void LTPushFront(LTNode* phead, LTDataType num);
//(三)指定位置插入节点
void LTInsert(LTNode* pos, LTDataType num);
//(四)指定位置之后插入节点
void LTInsertAfter(LTNode* pos, LTDataType num);

//三、节点的删除
//(一)尾删
void LTPopBack(LTNode* phead);
//(二)头删
void LTPopFront(LTNode* phead);
//(三)指定位置删除节点
void LTErase(LTNode*& pos);
//(四)指定位置之后删除节点
void LTEraseAfter(LTNode* pos);

List.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//一、节点的创建、初始化、打印链表、判断头节点、查找节点、销毁链表
//(一)节点的创建并初始化
LTNode* Buy_node(LTDataType num)
{
	//节点的创建
	LTNode* new_node = (LTNode*)malloc(sizeof(LTNode));
	if (new_node == nullptr)
	{
		perror("Buy_node malloc fail");
		exit(1);
	}

	//节点的初始化
	new_node->data = num;
	new_node->next = new_node->prev = new_node;

	return new_node;
}
//(二)链表的初始化
LTNode* LTInit()
{
	LTNode* plist = Buy_node(-1);
	return plist;
}
//(三)打印链表
void LTPrint(LTNode* phead) 
{
	LTNode* pcur = phead->next;
	cout << "-> ";
	while (pcur != phead)
	{
		cout << pcur->data << " -> ";
		pcur = pcur->next;
	}
	cout << endl;
}
//(四)判断是否只有头节点
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
//(五)查找节点
LTNode* LTFine(LTNode* phead, LTDataType num)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == num)
			return pcur;
		pcur = pcur->next;
	}
	return nullptr;
}
//(六)销毁链表
void LTDestroy(LTNode*& phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next_noed = pcur->next;

		//next_noed->prev = phead;
		//phead->next = next_noed;
		free(pcur);
		pcur = next_noed;
	}
	free(phead);
	pcur = phead = nullptr;
}

//二、节点的插入
//(一)尾插
void LTPushBack(LTNode* phead, LTDataType num)
{
	LTNode* new_node = Buy_node(num);

	new_node->next = phead;
	new_node->prev = phead->prev;

	phead->prev->next = new_node;
	phead->prev = new_node;

}
//(二)头插
void LTPushFront(LTNode* phead, LTDataType num)
{
	LTNode* new_node = Buy_node(num);

	new_node->next = phead->next;
	new_node->prev = phead;

	phead->next->prev = new_node;
	phead->next = new_node;

}
//(三)指定位置插入节点
void LTInsert(LTNode* pos, LTDataType num)
{
	assert(pos);
	LTNode* new_node = Buy_node(num);
	LTNode* pre_node = pos->prev;

	new_node->next = pos;
	new_node->prev = pre_node;

	pos->prev = new_node;
	pre_node->next = new_node;

}
//(四)指定位置之后插入节点
void LTInsertAfter(LTNode* pos, LTDataType num)
{
	assert(pos);
	LTNode* new_node = Buy_node(num);
	LTNode* after_node = pos->next;

	new_node->next = after_node;
	new_node->prev = pos;

	after_node->prev = new_node;
	pos->next = new_node;
}


//三、节点的删除
//(一)尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);//空指针不可删除,需要判断
	assert(!LTEmpty(phead));//判断是不是只有头节点

	LTNode* del = phead->prev;
	LTNode* pre = del->prev;

	pre->next = phead;
	phead->prev = pre;

	free(del);
	del = nullptr;
}
//(二)头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* del = phead->next;
	LTNode* del_after = del->next;

	del_after->prev = phead;
	phead->next = del_after;

	free(del);
	del = nullptr;
}
//(三)指定位置删除节点
void LTErase(LTNode*& pos)
{
	assert(pos);
	LTNode* pre_node = pos->prev;
	LTNode* after_node = pos->next;

	pre_node->next = after_node;
	after_node->prev = pre_node;

	free(pos);
	pos = nullptr;
}
//(四)指定位置之后删除节点
void LTEraseAfter(LTNode* pos)
{
	assert(pos);

	LTNode* del = pos->next;
	LTNode* after_node = del->next;

	after_node->prev = pos;
	pos->next = after_node;

	free(del);
	del = nullptr;

}
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

void test()
{
	LTNode* plist = Buy_node(-1);
	//LTPrint(plist);

	for (int i = 1; i < 11; i++)
		LTPushBack(plist, i);
		//LTPushFront(plist, i);
	LTPrint(plist);

	/*for (int i = 0; i < 5; i++)
		LTPopFront(plist);
	LTPrint(plist);*/

	//LTNode* re_pos =  LTFine(plist, 10);
	//LTInsertAfter(re_pos, 255);
	//LTEraseAfter(re_pos);

	LTDestroy(plist);

	//LTPrint(plist);
}

int main()
{ 
	test();
	return 0;
}

        总结:

                ① 第一个节点指的是第一个有效的节点,而哨兵位是无效的节点(头节点);

                ② 插入时修改节点的顺序:先修改插入的新节点,再修改前一个指针(head->prev )和head指针的指向;(先改新节点,然后从后向前改 )

                ③ 在哨兵位之前插入,也是尾插;

四、顺序表与链表的分析


        以上内容仅供分享,若有错误,请多指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2135889.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pdf怎么加页码?5种pdf添加页码指南分享,快来领取!

如何在一个包含大量页面的大型pdf文件中快速找到特定的页面或信息呢&#xff1f;最简便的方法就是为pdf添加页码。pdf添加页码能够清晰显示页面顺序&#xff0c;帮助读者轻松浏览大型pdf文档&#xff0c;同时也便于寻找特定章节和确定整体长度。然而&#xff0c;并非所有pdf文件…

VirtualBox Install MacOS

环境搭建 git clone https://github.com/myspaghetti/macos-virtualbox 脚本配置 修改macos-guest-virtualbox.sh部分内容为 vm_name"macOS" # name of the VirtualBox virtual machine macOS_release_name"Catalina" # install &quo…

PHP 环境搭建教程

搭建一个稳定的PHP开发环境是开发Web应用的基础。在Linux系统上&#xff0c;LAMP&#xff08;Linux, Apache, MySQL/MariaDB, PHP&#xff09;堆栈是最广泛使用的组合。本文将详细介绍如何在Linux上搭建PHP开发环境&#xff0c;涵盖安装步骤、配置和测试。更多内容&#xff0c;…

Docker操作MySQL

1&#xff0c;拷贝&#xff1b; docker cp mysql01:/etc/mysql .2&#xff0c;修改conf.d和mysql.conf.d文件 3&#xff0c; vim mysql/my.cnf 4&#xff0c;拷贝并替换my.cnf文件 5&#xff0c;mysql镜像重启命令&#xff1a; docker exec -it mysql01 -uroot -p0000006&…

LOAM学习

LOAM Ceres Solver 中的LocalParameterization理解ALOAM雷达里程计主要步骤论文A-LOAM laser Odometry代码LiDAR Odometry寻找角点特征代码流程分析寻找面点特征 求解器设置 Ceres Solver 中的LocalParameterization理解 该LocalParameterization类用来解决非线性优化中的过参…

最全的软件测试面试题(含答案)

软件的生命周期(prdctrm) 计划阶段(planning)-〉需求分析(requirement)-〉设计阶段(design)-〉编码(coding)->测试(testing)->运行与维护(running maintrnacne) 测试用例 用例编号  测试项目  测试标题  重要级别  预置条件  输入数据  执行步骤   预期结果 1…

python做游戏好用吗

Python做游戏是完全可以的&#xff0c;而且也非常简单&#xff0c;有一个专门针对游戏开发的平台&#xff08;模块&#xff09;—pygame&#xff0c;允许开发人员快速设计游戏而又摆脱了低级语言的束缚&#xff0c;下面我简单介绍一下这个模块的安装和使用&#xff1a; 1、首先…

Java手写RPC框架-01-开篇

项目背景 随着业务不断升级&#xff0c;系统规模不断扩大&#xff0c; 单体架构会产生越来越多的问题&#xff0c;需要引入微服务将原先架构解耦为一个个模块。每个服务模块放在不同的服务器上&#xff0c;能够保证系统在高并发环境下的正常运转。 各个服务模块之间如何相互调…

想了解医疗大模型吗?请看《智能系统学报》实验室最新综述论文

本文改编自实验室的最新综述论文《医疗领域的大型语言模型综述》&#xff0c;该论文发表于《智能系统学报》。《智能系统学报》是中国人工智能学会会刊、“中国人工智能学会推荐中文学术期刊”列表中的A类期刊。该论文合作单位包括上海理工大学、上海儿童医学中心、复旦大学附属…

LangChain-Chatchat本地搭建部署

文章目录 前言一、安装部署1.软硬件要求2. 安装 Langchain-Chatchat3.安装Xinference4.遇到的问题问题1&#xff1a;Failed building wheel for llama-cpp-python问题2&#xff1a;Failed building wheel for pynini问题3&#xff1a;运行xinference错误 二、初始化项目配置并运…

了解软件测试的概念

本文我们来了解软件测试 的一些基本概念。同时需要记住衡量软件测试结果的依据—需求&#xff1b; 1. 需求的概念 满足用户期望或正式规定文档&#xff08;合同、标准、规范&#xff09;所具有的条件和权能&#xff0c;包含用户需求和软件需求。&#xff08;其实就是客户想要软…

摩尔信使MThings逻辑控制实例——交通灯

摩尔信使MThings提供了强大的数据配置和逻辑控制功能&#xff0c;可为用户带来一种高效且直观的方式进行管理和控制交通灯系统。与传统的PLC&#xff08;可编程逻辑控制器&#xff09;相比&#xff0c;MThings的界面更加用户友好&#xff0c;使得即使是非专业的用户也能够轻松地…

在 Mac 中设置环境变量

目录 什么是环境变量&#xff0c;为什么它们重要&#xff1f;什么是环境变量&#xff1f;举个例子 如何查看环境变量如何设置和修改环境变量1. 临时设置环境变量2. 永久设置环境变量3. 修改现有环境变量 环境变量在开发中的应用在 Node.js 项目中使用环境变量在 Python 项目中使…

Certificate has expired(npm 安装strapi)

报错信息 解决方法 1、清空缓存&#xff0c;有时&#xff0c;损坏的缓存会导致连接问题 npm cache clean --force 2、切换到淘宝镜像源的 npm 注册表 npm config set registry https://registry.npmmirror.com/ 执行这两步后就可以执行自己想要安装的东西了&#xff0c;我是在执…

Uniapp + Vue3 + Vite +Uview + Pinia 实现购物车功能(最新附源码保姆级)

Uniapp Vue3 Vite Uview Pinia 实现购物车功能&#xff08;最新附源码保姆级&#xff09; 1、效果展示2、安装 Pinia 和 Uview3、配置 Pinia4、页面展示 1、效果展示 2、安装 Pinia 和 Uview 官网 https://pinia.vuejs.org/zh/getting-started.html安装命令 cnpm install pi…

云轴科技ZStack 获鲲鹏应用创新大赛2024上海赛区决赛一等奖

9月13日&#xff0c;鲲鹏应用创新大赛2024上海赛区决赛成功举办。经评委专家从方案创新性、技术领先性、商业前景以及社会价值四个维度严格评审&#xff0c;云轴科技ZStack参赛作品《ZStack鲲鹏原生开发方案》荣获上海赛区企业赛——原生开发赛道&#xff08;互联网&#xff09…

AI大模型系统实战:挑战与应用多领域,人工智能大模型的实际应用场景

AI大模型系统实战&#xff1a;挑战与应用多领域&#xff0c;人工智能大模型的实际应用场景 人工智能的新浪潮中&#xff0c;大模型系统已成为技术革新的重要驱动力。它们以其强大的学习能力和广泛的应用场景&#xff0c;正在重新定义我们与机器交互的方式。本文将深入探讨AI大模…

VS 如何显示构建的时间

Cherno 构建 Hazel 的时候会显示构建时间 VS -> Tools -> Options -> Projects and Solution -> VC Project Settings

UGit:腾讯自研的Git客户端新宠

UGit 是一款专门针对腾讯内部研发环境特点量身定制的 Git 客户端&#xff0c;其目标在于大幅提升开发效率以及确保团队协作的高度流畅性。UGit 能够良好地支持 macOS 10.11 及以上版本、Apple Silicon 以及 Win64 位系统。 可以下载体验一把。 https://ugit.qq.com/zh/index.…

GIS可视化软件:地理信息与遥感领域中的洞察之眼

在地理信息与遥感技术的广阔天地中&#xff0c;可视化软件如同一双洞察世界的明眸&#xff0c;将复杂的数据编织成生动、直观的画卷&#xff0c;为我们揭示地球的奥秘与城市的律动。本文将深入挖掘其技术核心、应用实例、未来趋势&#xff0c;探讨可视化软件如何为地理信息与遥…