数据结构----结构--线性结构--链式存储--链表

数据结构----结构--线性结构--链式存储--链表

1.链表的特点

空间可以不连续,长度不固定,相对于数组灵活自由

搜索:

时间复杂度O(n)

增删:

头增头删时间复杂度O(1)

其他时间复杂度为O(n)

扩展:单向循环链表的特性

从任意节点出发皆可遍历整个链表

2.链表的组成

链表由数据域和指针域组成

3.链表及其功能的实现

1.创建一个链表,并查看每一个链表中所存的值

c 复制代码
//在Visual Studio 2022编译器下的用c语言的写法
#include <stdio.h>
#include<stdlib.h>

typedef struct Node {
	int nValue;
	struct Node* pNext;
	
}List;

List* list() {
	List* m_phead = NULL;
	List* m_tail = NULL;
	int len;
	printf("请输入链表长度\n");
	scanf_s("%d", &len);
	while (len) {
		List* PTemp=(List*)malloc(sizeof(List));
		printf("请输入数据\n");
		int value;
		scanf_s("%d", &value);
		PTemp->nValue = value;
		PTemp->pNext = NULL;
		if (m_phead) {//不是空节点
			m_tail->pNext = PTemp;
		}
		else {//是空节点
			m_phead = PTemp;
		}
         m_tail = PTemp;
		len--;
	}
	return m_phead;
}

void ShowList(List* m_phead) {
	while (m_phead) {
		printf("%d  ", m_phead->nValue);
		m_phead = m_phead->pNext;
	}
}

int main() {
	List* list1=list();
	ShowList(list1);
	printf("\n");
	ShowList(list1);
	return 0;
}

思考如何将链表反向打印,不破环原有链表结构

方法:

1.暴力 时间复杂度O(n的平方) 空间复杂度O(1)

2.交换 时间复杂度O(n的平方) 空间复杂度O(看怎么使用交换来确定)

3.栈 时间复杂度O(n) 空间复杂度O(n)

4.头插法造新链表 时间复杂度O(n) 空间复杂度O(n)

5.数组 时间复杂度O(n) 空间复杂度O(n)

6.递归 时间复杂度O(n) 空间复杂度O(n)

这里用递归进行实现

c 复制代码
//此函数的定义及其实现依赖于上面的链表代码
void ReserveList(List* head) {
	if (head->pNext == NULL) {//如果到了最后一个节点
		printf("%d  ", head->nValue);//打印该节点
		return;//返回
	}
	ReserveList(head->pNext);//先处理下一个
	printf("%d  ", head->nValue);//打印当前节点
}

2.将链表进行反转(用不消耗空间的方法)

消耗空间的方法:

1.栈

2.数组

3.递归

4.头插法创建一个新链表

不消耗空间的方法

用三个指针来实现

1.分别记三个指针为头,拿,断

2.处理:(1)插入 :将拿的指针所指的节点的下一个节点改为头指针所指向的节点

​ (2)改变标记:头的指针所指向的节点变为拿的指针所指向的节点

​ 拿的指针所指向的节点变为断的指针所指向的节点

​ 断的指针所指向的节点为断的指针所指向的节点的下一个节点

代码实现
c 复制代码
//此函数的定义及其实现依赖于上面的链表代码
List* FanZhuan(List* P_head) {

	if (P_head == NULL || P_head->pNext == NULL) return P_head;
	List* NewHead = NULL;
	List* Na = P_head;
	List* Duan = P_head->pNext;
	
	while (Duan) {
		Na->pNext = NewHead;
		NewHead = Na;
		Na = Duan;
		Duan = Duan->pNext;
	}
	Na->pNext = NewHead;
	return Na;
}

3.将两个链表进行合并且按照链表中数据的大小进行排序

方法:

1.定义两个指针一个确定新表头之后指向新表头(后续会对这个指针进行操作),一个指向新表头之后进行返回

2.处理:用传入的两个指针比较两链表,之后在新链表进行尾添加,然后相应的指针移到下一个节点,直到两个指针中的其中一个指针指向为空结束循环

3.将有剩余链表与新链表的尾部进行连接

代码实现

c 复制代码
//此函数的定义及其实现依赖于上面的链表代码
List* HeBing(List* list1_head, List* list2_head) {
	if (!list1_head) {
		return list2_head;
	}
	if (!list2_head) {
		return list1_head;
	}

	List* newHead = NULL;
	List* HEAD = NULL;

	if (list1_head->nValue < list2_head->nValue) {//确定表头
		newHead = list1_head;
		HEAD = newHead;
		list1_head=list1_head->pNext;

	}
	else {
		newHead = list2_head;
		HEAD = newHead;
		list2_head = list2_head->pNext;
	}

	while (list1_head && list2_head) {//循环判断拼接链表
		if (list1_head->nValue < list2_head->nValue) {
			newHead->pNext = list1_head;
			newHead = newHead->pNext;
			list1_head = list1_head->pNext;
		}
		else {
			newHead->pNext = list2_head;
			newHead = newHead->pNext;
			list2_head = list2_head->pNext;
		}
	}

	if (list1_head) {
		newHead->pNext = list1_head;
	}
	if (list2_head) {
		newHead->pNext = list2_head;
	}
	return HEAD;
}

4.链表题目的练习

第一题(网址为https://leetcode.cn/problems/LGjMqU/)

题目:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → ... → Ln-1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → ...

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

解决:

方法一:

暴力(不用这个)

方法二:

第一步:将这个链表从中间拆成两个链表(如果不是偶数长度,则前一个链表比后一个长一个节点)

第二步:翻转第二个链表

第三步:将两个链表进行合并,合并的方法为先第一个链表的节点,再第二个链表的节点,以此类推

代码如下
c++ 复制代码
//这里的代码是c++语言下的
class Solution {
public:
    void reorderList(ListNode* head) {
        if(head->next==nullptr) return;
        int listsize=0;
        ListNode* headsize=head;
        while(headsize){//判断链表长度
            listsize++;
            headsize=headsize->next;
        }
        if(listsize==2){//如果链表长度为2,结束
            return;
        }
        headsize=nullptr;
        //将链表分成两个    
        int listsize2=listsize/2; //链表2长度
        int listsize1=listsize-listsize2;//链表1长度
        ListNode* Temp=nullptr;
        ListNode* head1=head;//链表一头节点
        ListNode* head2=head;//链表二

        if(listsize1==1){//将链表一与链表二断开 1
            Temp=head1;
        }
        while(listsize1--){//遍历获得链表二头节点
            head2=head2->next;
            if(listsize1==1){
                Temp=head2;//记录链表一的尾节点   
            }
        }
         Temp->next=nullptr;//将链表一与链表二断开 2
        Temp=nullptr;
        ListNode* Temp1=nullptr;
        ListNode* Temp2=nullptr;
       //将链表二进行翻转
       //链表长度大于1进行翻转
       if(listsize2>1){
            ListNode*NewlistHead=nullptr;
            ListNode*Na=head2;
            ListNode*Duan=head2->next;
            while(Duan){
                Na->next=NewlistHead;
                NewlistHead=Na;
                Na=Duan;
                Duan=Duan->next;
            }
            Na->next=NewlistHead;
            //将两链表进行拼接 第一种
                Temp1=head1;
                Temp2=Na;
       }
       else{//不大于1
        //将两链表进行拼接 第二种
            Temp1=head1;
            Temp2=head2;
       }
        while(1){
            ListNode* Temp3=Temp1->next;
            
            Temp1->next=Temp2;
            Temp1=Temp3;
           if(Temp2==nullptr||Temp1==nullptr){
              break;
            }

            ListNode* Temp4=Temp2->next;
            Temp2->next=Temp1;
            Temp2=Temp4;
             
        }
    }
};

第二题(网址为https://leetcode.cn/problems/3u1WK4/)

题目:

给定两个单链表的头节点 headAheadB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

解决:

方法一:

暴力(不用这个 用的时间和空间太多了)

方法二:

栈 (两个链表出栈时,进行比较,看是否有相同的)

方法三:

差值法:

​ 第一步:遍历两个链表获得两个链表的长度

​ 第二步:进行相减,获得长度差

​ 第三步:长的那个先走长度差的距离,之后两个指向链表的指针一起走,

​ 如果两个指针指向相同的节点时,结束找到了。

​ 如果两个链表指向空地址了还没找到,结束没找到。

这里用方法三来写,方法三的代码如下
c++ 复制代码
//这里的代码是c++语言下的
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int headAsize=0;
        int headBsize=0;
        ListNode*headA1=headA;
        ListNode*headB1=headB;
        int x=0;
        while(headA1){
            headAsize++;
            headA1=headA1->next;
        }
        while(headB1){
            headBsize++;
            headB1=headB1->next;
        }
        headA1=headA;
        headB1=headB;
       if(headAsize>=headBsize){
           x=headAsize-headBsize;
            while(x--){
                headA1=headA1->next;
            }
       }
       else{
            x=headBsize-headAsize;
              while(x--){
                headB1=headB1->next;
            }
       }
       while(1){
            if(headA1==headB1) return headA1;
           if(headA1==nullptr||headB1==nullptr) return 0;
           headA1=headA1->next;
           headB1=headB1->next;
       }
    }
};

第三题(网址为https://leetcode.cn/problems/c32eOV/)

题目:

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

**说明:**不允许修改给定的链表。

解决:
方法一

快慢指针:

第一步:用快慢指针找到交点(如果找不到节点就是无环)

​ 第二步:将交点断开(之后要还原)

​ 第三步:定义从交点和起始点开始的两个指针,遍历获得长度

​ 第四步:进行相减,获得长度差

​ 第五步:长的那个先走长度差的距离,之后两个指向链表的指针一起走,

​ 两个指针指向相同的节点时,返回该节点。

方法二:

将链表进行翻转,将链表每一次要翻转的链表进行一个存储(用指针数组存),之后在存储的这个指针数组中,数组的头和尾一起往中间遍历,当第一次找到不一样的节点时,返回上一次的节点就是入环点

方法三:

数学推导法:

​ 经过推导得 a=(R-1)(b+c)+c

​ 所以得出只要用两个指针指向A点和C点并且同时同速度出发,最后就会在B点相遇,可得入环点

这里用方法一来写,方法一的代码如下
c++ 复制代码
//这里的代码是c++语言下的
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==nullptr){//如果没节点返回空,无环
            return head;
        }
        if(head->next==nullptr){//如果只有一个节点返回空,无环
            return 0;
        }
        //定义快慢指针
        ListNode *Fast=head;
        ListNode *Slow=head;
        //定义一个指针指向交点
        ListNode *JiaoDian=nullptr;
        ListNode *JiaoDian2=nullptr;
        //快慢指针进行遍历找到交点
        while(1){
            if(Fast->next==nullptr){//如果快指针会指向为空,无环
                return Fast->next;
            }
            else if(Fast->next->next==nullptr){
                return Fast->next->next;
            }
            
            Fast=Fast->next->next;
            Slow=Slow->next;
            if(Fast==Slow){//找到交点
                JiaoDian=Fast;
                JiaoDian2=Slow=Slow->next;//断点的下一个
                Fast->next=nullptr;//将交点断开,一会要还原
                break;
            }
        }
        //定义从交点和起始点开始的指针
        ListNode *StartJiao=Slow;
        ListNode *Start=head;
        //遍历两个指针获得长度
        int StartJiaolen=0;
        int Startlen=0;
        while(StartJiao){//起点开始的长度
            StartJiaolen++;
            StartJiao=StartJiao->next;
        }
        while(Start){//断点开始的长度
            Startlen++;
            Start=Start->next;
        }
        StartJiao=Slow;//回到起点
        Start=head;
        int chalen=0;//相差长度
        if(StartJiaolen>Startlen){
            chalen=StartJiaolen-Startlen;
            while(chalen--){//移动交点指针
                StartJiao=StartJiao->next;
            }
        }
        else{
            chalen=Startlen-StartJiaolen;
            while(chalen--){//移动开始指针
                Start=Start->next;
            }
            
        }

        while(1){//移动找到入口点
            if(Start==StartJiao){
                JiaoDian->next=JiaoDian2;
                return Start;
            }
            if(Start->next==nullptr||StartJiao->next==nullptr){
                return 0;
            }
           Start=Start->next;
           StartJiao=StartJiao->next;
           
        }
    }
};

第四题(网址为https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/)

题目:

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

解决:
方法一:

​ 暴力 时间复杂度为O(n的平方) ,时间太久,不用此方法

方法二:

第一步:创建一个新链表(实现了next指针)

第二步:将两个链表交叉合并

第三步:实现(random指针)

第四步:将两个链表分开还原

第五步:返回新链表

时间复杂度为O(n) 空间复杂度为O(1)

代码如下
c++ 复制代码
//这里的代码是c++语言下的
class Solution {
public:
  
    Node* copyRandomList(Node* head) {
        if(head==nullptr){
            return head;
        }
        
        Node* BianLi=head;//用于遍历原来链表的指针
        Node* NewHead=nullptr;//新链表
        Node* Temp=nullptr;//用来指向新链表的表头
     
        //添加完成新链表(random指针还没有实现)

        NewHead=Temp=NewNode(*BianLi);//表头
        BianLi=BianLi->next;
        while(BianLi){
            Node* Temp2=NewNode(*BianLi);
            BianLi=BianLi->next;
            Temp->next=Temp2;
            Temp=Temp->next;
           
        }
       
        //将两个链表相交连接
        Temp=NewHead;//重置  用于遍历新链表的指针
        BianLi=head;//重置  用于遍历原来链表的指针
        while(1){
           
            Node* LinShi1=BianLi->next;//临时变量
            BianLi->next=Temp;
            BianLi=LinShi1;
            if(BianLi==nullptr){
                break;
            }
            
            Node* LinShi2=Temp->next;//临时变量
            Temp->next=BianLi;
            Temp= LinShi2;
        }

        //将random指针实现
        Node* Temp3=head;//用于遍历合并后两个链表的指针
        int bool1=1;//判断是不是原有链表的节点
        while(1){//循环将复制的节点的复杂指针实现

            if(bool1%2==1){
                if(Temp3==nullptr){//结束条件
                    break;
                }
                if(Temp3->random==nullptr){//如果指向为空,那对应的就给空,这里是因为我合并的那个链表只有一个nullptr
                    Temp3->next->random=nullptr;
                }
                else{
                     Temp3->next->random=Temp3->random->next;
                }
               
            }

            Temp3=Temp3->next;
           
            bool1+=1;
        }

        //将两个链表进行拆分
        Temp3=head;//重置 用于遍历合并后两个链表的指针
        while(1){
            if(Temp3==nullptr){//终止条件
                break;
            }
            Node* LinShi1=Temp3->next;//临时变量
            if(Temp3->next==nullptr||Temp3->next->next==nullptr){//最后两个数据的处理
                Temp3->next=nullptr;
            }
            else{
                Temp3->next=Temp3->next->next;
            }
             Temp3=LinShi1;
            
        }
        return NewHead;
    }

    //造新节点
     Node* NewNode(Node node){
        Node*newnode=(Node*)malloc(sizeof(Node));
        newnode->val=node.val;
        newnode->next=nullptr;
        newnode->random=nullptr;
        return newnode;
    }
};

5.如何判断一个数字是不是2的整数次幂(此题与链表无关)

整数为n 用n&(n-1)看是否等于0等于0就是2的整数次幂

6.找到一个数二进制是1的一位(随机的找是1的一位)(此题与链表无关)

整数为n 用n&(-n)就可以找到了

7.跳跃列表:SkipList(并不是一个链表结构)

跳跃列表基于有序链表

1.跳表查找的实现

1.根据链表的长度判断有几层 层数=log2的链表长度

2.每一层有哪些元素是根据概率来定的(每一个元素是否有的概率都是二分之一)

3.然后找数是从高层往低层找 在每一层跟链表中的元素做比较大于就取右边,小于就取左边,等于就是找到了(这一步和二分类似)

4.到最后一层看是否找到

2.跳表添加的实现

1.根据链表的长度判断有几层 层数=log2的链表长度

2.每一层有哪些元素是根据概率来定的(每一个元素是否有的概率都是二分之一)

3.然后从高层往低层 依次找到要添加的元素在每层该插入的地方,并存起来

4.根据概率判断每一层是否都有这个元素(概率是二分之一)

5.进行插入

8.哈希表(散列表):hashTable

1.确定分组

用求整取余法 公式为:P=key%M(M是个数) (会产生哈希冲突问题)

2.定哈希冲突解决方案

1.开放定址法:

​ 1.线性探测

​ 2.二次探测

2.拉链法:

​ 第一步:定义一个链表结构体

​ 第二步:申请指针数组(数组中每个元素初值为空)

​ 第三步:元素入组(头插法)

​ 第四步:查找

用拉链法实现简单哈希表代码如下(此代码是用c语言写的)

c 复制代码
#include <stdio.h>
#include<stdlib.h>
#include<windows.h>
typedef struct Node {
	int nValue;
	struct Node* pNext;
	                                                                                
}List;

List* list() {
	List* m_phead = NULL;
	int len = 1;
	int date;
	printf("请输入数据\n");
	scanf_s("%d", &date);
	
	m_phead = (List*)malloc(sizeof(List));
	m_phead->nValue = date;
	m_phead->pNext = NULL;
	return m_phead;
}

//申请指针数组
List** Array(int n) {
	List** array_head = (List**)(malloc(sizeof(List*) * n));
	memset(array_head, 0, (sizeof(List*) * n));
	return array_head;
}
//头插法
void pushhead(List* lst, List** arr) {
	lst->pNext = (*arr);
	*arr = lst;
}

//元素入组
void TianJia(int x,int n, List* lst, List** arr) {

	int weiyi = x % n;
	List** temp = arr;
	while (weiyi--) {
		temp++;
	}
	if (*temp) {
		pushhead(lst, temp);
	}
	else {
		*temp = lst;
	}
}



void ShowList(List* m_phead, int x) {
	while (m_phead) {
		if (m_phead->nValue == x) {
			printf("找到了");
			printf("%d", x);
			return;
		}
		m_phead = m_phead->pNext;
	}
	printf("没找到");
}
//查找
void find(int x, int n, List** arr) {
	int y = x % n;
	arr += y;
	ShowList(*arr, x);
}


int main() {
	int n = 5;
	//申请指针数组
	List** arr=Array(n);
	List* lsti1 = list();
	TianJia(lsti1->nValue,n, lsti1, arr);//元素入组,
	List* lsti2 = list();
	TianJia(lsti2->nValue, n, lsti2, arr);//元素入组
	List* lsti3 = list();
	TianJia(lsti3->nValue, n, lsti3, arr);//元素入组
	List* lsti4 = list();
	TianJia(lsti4->nValue, n, lsti4, arr);//元素入组
	List* lsti5 = list();
	TianJia(lsti5->nValue, n, lsti5, arr);//元素入组
	find(2, n, arr);//查找元素
	return 0;
}

3.线性探测的优化

装载因子 α=元素/表长 <0.8 越趋近于0.8冲突的可能性越高

优化的方法就是申请的空间大些尽量让装载因子小于0.8

4.拉链法与线性探测优化各自的优势

拉链法:

​ 1.处理冲突简单

​ 2.删除数据容易

​ 3.适用于未知元素个数的情况

​ 4.处理元素所占空间大且多的情况,用的空间少

线性探测优化:

​ 处理元素所占空间小且少的情况,用的空间少

相关推荐
闪电悠米3 小时前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记4 小时前
堆(数据结构)
数据结构·
wabs6668 小时前
关于图论【卡码网100.最大岛屿的面积的思考】
数据结构·算法·图论
yuannl109 小时前
图的最短路径Dijkstra
数据结构
zander25810 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态10 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面11 小时前
栈在递归中的应用
数据结构·算法
皓月斯语11 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king11 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
Adios79419 小时前
设置交集大小至少为2
数据结构·算法·leetcode