前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据结构(二): 链表篇

数据结构(二): 链表篇

作者头像
看、未来
发布2021-09-18 10:09:19
2800
发布2021-09-18 10:09:19
举报
文章被收录于专栏:CSDN搜“看,未来”
在这里插入图片描述
在这里插入图片描述

文章目录

C链表

链表在C语言的数据结构中的地位可不低。后面很多的数据结构,特别是树,都是基于链表发展的。 所以学好链表,后面的结构才有看的必要。

初识链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

链表有很多种不同的类型:单向链表,双向链表以及循环链表。


单链表

在这里插入图片描述
在这里插入图片描述
单链表实现

话不多说啊,这里我只想直接放代码:

代码语言:javascript
复制
#include<iostream>

using namespace std;

class ListNode {
private:
	int value;	//值域
	struct ListNode* next;	//指针域

public:
	ListNode(int value) {
		this->value = value;
		this->next = NULL;
	}

	void add_node(ListNode* next_node) {
		this->next = next_node;
	}

	void set_value(int value) {
		this->value = value;
	}

	int get_value() {
		return this->value;
	}

	ListNode* get_next() {
		return this->next;
	}
	
	//创建结点
	ListNode* creat(int data ){								
		ListNode*p=NULL;
		
		p = new ListNode();
		
		if(NULL == p){
			cout<<"申请内存失败"<<endl;
			exit(-1);
		}
		
		p->value=data;
		p->next=NULL;	//处理干净身后事
		return p;
	}
	
	//新增节点	这里默认头已经有了
	void add(ListNode* the_head,int data )	{
		ListNode* pNode = the_head;
		
		//后面再接上一个
		while (pNode->next != NULL){							//遍历链表,找到最后一个节点
			pNode = pNode->next;
		}
		pNode->next = new ListNode(data);	
	}
	
	//删除节点
	void del(ListNode* the_head, int index){	//删除第index个节点
		ListNode* pFree = NULL;					//用来删除
		
		if(index < 1){
			return;
		}
		
		if(1 == index){
			pFree = the_head;
			head = the_head->next;
			
			free(pFree->pData);				//先释放数据
			free(pFree);					//释放指针
			
			return;
		}
		
		ListNode* pNode = the_head;
		
		while (pNode->next != NULL && index>2){
			pNode = pNode->next;
		}	
		
		pFree = pNode->next;				//再指向数据域就爆了
		pNode->next=pNode->next->next;	//这里要无缝衔接
		free(pFree->pData);				//先释放数据
		free(pFree);					//释放指针
	}
	
	//计算节点数
	int Count(ListNode* the_head){
		int count = 0;
		ListNode*pNode = the_head;
		
		while (pNode != NULL){
			pNode = pNode->next;
			count++;		
		}	
		return count;
	}
	
	//查找固定节点数据
	ListNode* find(ListNode* the_head,int index){

		ListNode* pNode=NULL;
	
		pNode=the_head;
		
		while(pNode != NULL && index){
			pNode = pNode->next;
			index--;		
		}

		if(NULL == pNode){
			cout<<"节点不存在"<<endl;	//可以写日志里
		}
		return pNode;
	}
};
代码语言:javascript
复制
//判断链表成环
bool is_ring(ListNode* a) {
	ListNode* fast = a,*slow = a;
	while (fast->get_next()) {
		fast = fast->get_next();
		if (fast->get_next() && slow->get_next() && fast != slow) {
			fast = fast->get_next();
			slow = slow->get_next();
		}

		if (fast == slow) {
			return true;
		}
	}
	return false;
}

//寻找入环点(默认成环)
int find_ring(ListNode* a) {
	ListNode* fast = a->get_next()->get_next(), * slow = a->get_next();

	while (fast != slow) {
		fast = fast->get_next()->get_next(); 
		slow = slow->get_next();
	}

	ListNode* slow2 = a;

	while (fast != slow2) {
		fast = fast->get_next()->get_next();
		slow2 = slow2->get_next();
	}

	return slow2->get_value();
}


//原地翻转链表
ListNode* reverse_list(ListNode* a) {
	ListNode* temp = new ListNode(0);

	ListNode* h = NULL;

	while(a->get_next()) {
		temp = a;
		a = a->get_next();
		temp->add_node(h); 
		h = temp;
	}
	return temp;
}

尾插法

若将链表的左端固定,链表不断向右延伸,这种建立链表的方法称为尾插法。尾插法建立链表时,头指针固定不动,故必须设立一个搜索指针,向链表右边延伸,则整个算法中应设立三个链表指针,即头指针head、搜索指针p2、申请单元指针pl。尾插法最先得到的是头结点。

上面那个就是。


循环链表

在这里插入图片描述
在这里插入图片描述
寻找链表入环点

这个就比较需要脑子了,前边那个有手就行的。

我这个人,懒了点,来张现成的图吧。

在这里插入图片描述
在这里插入图片描述

看啊,在相遇之前呢,慢指针走的距离很好求的:L1 = D+S1; 快指针走的距离:设它在相遇前绕了n圈(n>1),那么:L2 = D+S1+n(S1+S2);

不过,还有一个等量关系,不要忽略掉,快指针的速度是慢指针的两倍,所以:L2 = 2L1; 那么就是:n(S1+S2)-S1 = D; 再转换一下就是:(n-1)(S1+S2)+S2 = D;

那也就是说,在相遇时候,把一个慢指针放在链表头,开始遍历,把一个慢指针放在相遇点开始转圈,当它俩相遇的时候,就是入环点了。

其实吧,用脑子想一开始很难想出来,用手想就快多了。

环的大小就不用我多说了吧,相遇之后,定住快指针,慢指针再绕一圈,再相遇的时候就是一圈了。


双向链表

在这里插入图片描述
在这里插入图片描述

参考单链表。


旋转链表

这个也比较有意思啊,题目时这样的:给定一个当链表,让你顺时针/逆时针旋转N个位置,要求原地旋转。

我讲一下思路吧: 1、将链表自成环。 2、从刚刚的头往后遍历N个位置,N为要旋转的数。 3、环断开。

解决。 秀吧,我就是觉得解法好玩,就收藏了。


STL 中的 List

每一个自己写过链表的人都知道,链表的节点和链表本身是分开设计的。 那我们来看一下List的节点设计:

代码语言:javascript
复制
template <typename T>
struct __list_node
{
	typedef void* void_pointer;
	void_pointer prev;
	void_pointer neet;
	T date;
}

显而易见,这是一个通用双向链表的节点(如果对通用链表不了解,建议一定要自己动手设计一个)。

在这里插入图片描述
在这里插入图片描述

3、List基本函数使用

代码语言:javascript
复制
#include<list>

typedef struct rect
{
	···
}Rect;

list<Rect>test;	//声明一个链表,用于存放结构体数据

//如果想以其他方法初始化list列表,可以移步到下一行那个Vector的介绍

代码语言:javascript
复制
Rect a;
···
test.push_back(a);
test.push_front(a);
//头尾插入(双向链表)

//定点插入
test.insert(test.begin()+10,a);	//在第十个节点之前插入a

代码语言:javascript
复制
//删除test头部的元素
test.erase(test.begin());

//删除test从头到尾的元素
test.erase(test.begin(), test.end());

test.pop_back();
test.pop_front();

其实增删还是推荐使用迭代器来,因为直接对数据进行操作会存在一定的危险。 在本文第三部分详细讲述了List迭代器操作增删。

除了这个函数:test.clear();这个函数安全得很,反正都清理掉了。


  1. 查、改
代码语言:javascript
复制
//迭代器
list<int>::iterator p;
for (p = test.begin(); p != test.end(); p++)
	cout << *p << " ";

要遍历还是首推迭代器。所有和遍历有关的还是用迭代器。


代码语言:javascript
复制
#include<algorithm>
sort(test.begin(),test.end());	//从头到尾,默认从小到大排序

//一般排序都是要自定义排序的:
bool Comp(const int &a,const int &b)
{
    return a>b;
}
sort(test.begin(),test.end(),Comp);	//这样就降序排序。 

  1. 大小
代码语言:javascript
复制
test.size();	//容器已存入数据量
test.capacity();	//容器还能存多少数据量

//其实不用担心容器不够大,容量要满的时候它会自己扩容
  1. 其他 (1)压缩list
代码语言:javascript
复制
//去除重复的元素至只保留一个副本
test.unique();
//已经过大小排序的list才能使用
代码语言:javascript
复制
	(2)合并list
代码语言:javascript
复制
test.splice(test.end(),test2);//将test2剪切到test后面

最后还是要提一下头文件: 分不清楚就都写上

代码语言:javascript
复制
#include<algorithm>
#include<list>

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/02/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • C链表
    • 初识链表
      • 单链表
        • 单链表实现
        • 尾插法
      • 循环链表
        • 寻找链表入环点
      • 双向链表
        • 旋转链表
        • STL 中的 List
          • 3、List基本函数使用
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档