数据结构与算法之单链表

数据结构与算法之单链表



链表概念

链表是一种通过指针串联起来的线性结构,每一个节点由两部分组成,存放数据的部分称为数据域;存放指针的部分被称为指针域,指针域存放指向下一个节点的指针(最后一个节点的指针域指向空指针)。
结点
单个结点图如下:
在这里插入图片描述

节点结构c++代码

struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};

链表在内存中的存贮方式

数组元素在内存空间上是连续分布的,而链表的节点在内存空间上不是连续分布的,链表通过指针域的指针来连接分布在内存中的各个节点。
单链表如下所示:
在这里插入图片描述
注意:
1、链表的插入、删除结点的时间复杂度为O(1)。
2、访问链表中的某个结点的时间复杂度为O(n)。

关于链表的各种操作

链表类

说明:类中使用虚头节点是使得删除(插入)头节点与删除(插入)其它节点代码一致,另外假设头节点对应索引为0

struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};

class MyLinkedList {
public:
	MyLinkedList():_dummy(new ListNode(0)), _size(0){ }
	~MyLinkedList(){ 
		clear();
		delete _dummy;
		_dummy = nullptr;
	}
	int get(int index)const;
	bool insertNode(int index, int val);
	bool deletNode(int index);
	void clear();
	void reverse();
	int midNode();
private:
	ListNode *_dummy; //虚头结点
	int _size; //链表中结点个数
};

1、查找指定索引处的元素

假设头节点对应索引为0,其他节点索引依次递增,并且索引无效将返回-1

int MyLinkedList::get(int index)const{
	if(index < 0 || index >= _size)
		return -1;
	ListNode *cur = _dummy->next;
	while(index){
		cur = cur->next;
		--index;
	}
	return cur->val;
}

2、在指定索引处插入一个节点

例如在索引2处插入一个值为6的节点
插入前:
在这里插入图片描述

插入后
在这里插入图片描述
在进行插入元素操作时我们需要找到该位置的前一个位置的节点,然后再进行插入

bool MyLinkedList::insertNode(int index, int val){
	if(index < 0 || index > _size)
		return false;
	ListNode *pre = _dummy;
	ListNode *cur = _dummy->next;
	while(index){
		pre = cur;
		cur = cur->next;
		--index;
	}
	ListNode *newNode = new ListNode(val);
	pre->next = newNode;
	newNode->next = cur->next;
	++_size;
	return true;
}

3、删除指定索引处的节点

例如要删除索引2处的节点
删除前:
在这里插入图片描述
删除后:
在这里插入图片描述

bool MyLinkedList::deletNode(int index){
	if(index < 0 || index >= _size)
		return false;
	ListNode *pre = _dummy;
	ListNode *cur = _dummy->next;
	while(index){
		pre = cur;
		cur = cur->next;
		--index;
	}
	pre->next = cur->next;
	delete cur;
	--_size;
	return true;
}

4、清空链表

void MyLinkedList::clear(){
	ListNode *cur = _dummy->next;
	while(cur != nullptr){
		ListNode *node = cur->next;
		delete cur;
		cur = node;
	}
	_dummy->next = nullptr;
	_size = 0;
}

5、翻转链表

在这里插入图片描述

void MyLinkedList::reverse(){
	if(_size == 0)
		return _dummy;
	ListNode* pre = nullptr;
	ListNode* cur = _dummy;
	while(cur != nullptr){
		ListNode* node = cur->next;
		cur->next = pre;
		pre = cur;
		cur = node;
	}
	_dummy->next = pre;
}

6、找链表的中间节点

利用双指针实现:定义一个慢指针slow,每次走一个节点;定义一个快指针fast,每次走两个节点,slow和fast都从头节点出发,当fast->next 为空指针或者fast->next->next为空指针时,slow指向的就是中间节点

int MyLinkedList::midNode(){
	ListNode* slow = _dummy->next;
	ListNode* fast = _dummy->next;
	while(fast->next != nullptr && fast->next->next != nullptr){
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow->val;
}

7、判断链表是否有环

该函数适合没有虚头节点的链表,所以不作为成员函数
在这里插入图片描述
利用双指来实现:定义一个慢指针slow,每次走一个节点;定义一个快指针每次走两个节点,这两指针都从头节点出发,若存在环则最终这两个指针会相遇,否则没有环

bool circle(ListNode* head){
	ListNode* slow = head;
	ListNode* fast = head;
	while(fast->next != nullptr && fast->next->next != nullptr){
		slow = slow->next;
		fast = fast->next->next;
		if(fast == slow)
			return true;
	}
	return false;
}

8、若有环如何找环的入口呢?

说明:该函数适合没有虚头节点的链表,所以不作为成员函数
在这里插入图片描述
假设头结点到环形入口的节点数为x,入口节点到快慢指针相遇节点的节点数为y,相遇点到入口的节点数为z。在这里slow指针走一个节点,fast指针走两个节点。
当两个指针相遇时,slow指针走过的节点数为:x+y。fast指针走过的节点数为:x+y + n(y + z),其中n表示相遇前fast指针走过的圈数。slow指针走一个节点,fast指针走两个节点,则 2(x+y) = x+y + n(y + z)。最终x = n(y + z) - y = (n - 1)(y + z) + z,在这里n至少为1。
当n = 1时,x = z,于是我们可以在快慢指针相遇时,在设置一个从头节点出发的指针cur,让cur和slow同时运动,最终cur和slow的相遇点即为环入口。

ListNode* findCircleEnter(ListNode* head){
	ListNode* slow = head;
	ListNode* fast = head;
	while(fast->next != nullptr && fast->next->next != nullptr){
		slow = slow->next;
		fast = fast->next->next;
		if(fast == slow){
			ListNode* cur = head;
			while(cur != slow){
				cur = cur->next;
				slow = slow->next;
			}
			return cur;
		}
	}
	return nullptr;
}

总结

1、链表的插入和删除的时间复杂度为O(1)。
2、访问链表中某个节点的时间复杂度为O(n)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值