数据结构 -- 链表

本文介绍了链表的两种常见形式:单链表和循环链表。单链表通过头指针访问,操作尾部元素需要遍历整个链表,适合频繁的插入和删除操作。循环链表则允许从任意节点开始遍历,通过尾指针访问最后一个节点的时间复杂度为O(1)。此外,还提到了双向链表,它的灵活性更高但占用更多存储空间。

单链表(ForwardList)

链表是链式存储结构的线性表的实现,由于内存地址不连续,所以链表不仅要有数据域,还要存储后继结点指针,如果一个结点只有一个指向后继结点的指针域,那么该线性表称为单链表

头指针与头结点

头指针中存储的是第一个结点的内存地址,通过头指针找到第一个结点,之后通过第一个结点的指针域可以找到第二个结点,以此类推,可以找到链表的最后一个结点,最后一个结点的指针域为空(nullptr)
为了更方便的对链表进行操作,引入了一个头结点的概念,头结点和普通结点一样,分为数据域和指针域,数据域可以不存东西也可以存如线性表的长度等附加信息,指针域存储的是第一个结点的内存地址
头指针和头结点的对比如下:
头指针

  • 头指针是指链表指向第一个结点的指针,若链表有头结点,则指向头结点
  • 头指针是链表的必要元素

头结点

  • 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可以存放链表的长度)
  • 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了
  • 头结点不一定是链表必须要素

如下实现使用头指针操作链表,遍历链表只能从头指针开始,所以访问链表中第一个结点的时间复杂度为O(1),访问最后一个结点的时间复杂度为O(N)

核心函数如下:

函数功能时间复杂度
push_front向链表头部添加元素O(1)
pop_front移除链表头部的元素O(1)
push_back向链表尾部添加元素O(N)
pop_back移除链表尾部的元素O(N)

单链表操作尾部的元素需要从头部遍历到尾部之后才能操作,所以时间复杂度为O(N)
当然可以维护一个尾指针指向最后一个结点,但是这相当于空间换时间

#ifndef FORWARDLIST_H_
#define FORWARDLIST_H_

#include <iostream>

#include <tools/base/copyable.h>
#include <tools/base/noncopyable.h>

template <typename T>
class ForwardList : copyable {
private:
	struct Node : noncopyable {
		T data;
		Node *next;

		Node(T elem) : data(elem), next(nullptr) {}
	};

public:
	ForwardList() : 
		m_head(nullptr), 
		m_size(0){}                                 // 默认构造函数

	ForwardList(const ForwardList& other) :         // 拷贝构造函数
		m_head(nullptr), 
		m_size(other.size())
	{         
		if(other.empty()){
			return;
		}

		m_head = new Node(other.head()->data);
		if(other.size() == 1){
			return;
		}

		Node *preNode = m_head;
		Node *selfNode;
		Node *otherNode = other.head()->next;
		while(otherNode != nullptr){
			selfNode = new Node(otherNode->data);
			preNode->next = selfNode;
			preNode = preNode->next;
			otherNode = otherNode->next;
		}
	}

	~ForwardList(){
		Node *curNode = m_head;
		Node *nextNode;
		while(curNode != nullptr){
			nextNode = curNode->next;
			delete curNode;
			curNode = nextNode;
		}
	}

	int size() const {
		return m_size;
	}

	bool empty() const {
		return m_size == 0;
	}

	Node *head() const {
		return m_head;
	}

	void push_front(T elem){                        // 向链表头部添加元素
		Node *node = new Node(elem);
		node->next = m_head;
		m_head = node;

		++ m_size; 
	}

	void pop_front(){                               // 移除链表头部的元素
		if(m_head == nullptr){
			return;
		}

		Node *node = m_head->next;
		delete m_head;
		m_head = node;

		-- m_size;
	}

	void push_back(T elem){                         // 向链表尾部添加元素
		Node *node = new Node(elem);
		if(m_head == nullptr){
			m_head = node;
		}
		else{
			Node *tailNode = m_head;
			while(tailNode->next != nullptr){
				tailNode = tailNode->next;
			}
			tailNode->next = node;
		}

		++ m_size;
	}

	void pop_back(){                                // 移除链表尾部的元素
		if(m_head == nullptr){
			return;
		}

		if(m_size == 1){
			delete m_head;
			m_head = nullptr;
			-- m_size;
			return;
		}

		Node *preNode = m_head;
		Node *tailNode = preNode->next;
		while(tailNode->next != nullptr){
			preNode = tailNode;
			tailNode = tailNode->next;
		}
		preNode->next = nullptr;
		delete tailNode;

		-- m_size;
	}

	void operator=(const ForwardList &other){       // 重载赋值运算符
		this->~ForwardList();

		m_head = nullptr;
		m_size = other.size();

		if(other.empty()){
			return;
		}

		m_head = new Node(other.head()->data);
		if(other.size() == 1){
			return;
		}
		
		Node *preNode = m_head;
		Node *selfNode;
		Node *otherNode = other.head()->next;
		while(otherNode != nullptr){
			selfNode = new Node(otherNode->data);
			preNode->next = selfNode;
			preNode = preNode->next;
			otherNode = otherNode->next;
		}
	}

	// for pratice and it doesn't
	void insert(int index, T elem){}                // 向指定位置插入元素

	void remove(int index){}                        // 删除指定位置的元素

	void set(int index, T elem){}                   // 修改指定位置的元素

	T& get(int index) const {}                      // 获取指定位置的元素

	// for unit test
	void print(const char *name) const {
		std::cout << name << "(ForwardList): size = " << size() << '\n';
		std::cout << "data = ";
		Node *curNode = m_head;
		while(curNode != nullptr){
				std::cout << curNode->data << " -> ";
				curNode = curNode->next;
		}
		std::cout << "nullptr" << std::endl;
	}

private:
	Node *m_head;
	int m_size;
};

#endif // FORWARDLIST_H_

单链表的优缺点

优点:单链表是一个完全的动态数据结构,不像动态数组那样需要拷贝元素进行扩容
缺点:除了向链表的头部添加和删除元素的时间复杂度是O(1),添加和删除元素整体的时间复杂度是O(N),修改和查看链表中的元素的时间复杂度也是O(N)
整体来看,链表“增删改查”的时间复杂度都是O(N),并没有动态数组有优势,但是在同一个位置一次插入多个数据的话,那么链式存储结构优势就明显了,因为只有在插入第一个数据时需要遍历查找,之后的数据就不需要了,所以说:对于插入和删除数据越频繁的情况下,使用链表效率会越高

循环链表(CircleList)

循环链表的每个结点也是有一个数据域和一个指针域构成,与单链表不同的是链表最后一个结点的指针域指向第一个结点,这样链表就围成了一个环
循环链表的好处是:从链表中的任意一个结点开始都能遍历完整个链表
循环链表中使用尾指针来操作循环链表,这样访问最后一个结点和第一个结点的时间复杂度就都是O(1)

核心函数如下:

函数功能时间复杂度
push_front向链表头部添加元素O(1)
pop_front移除链表头部的元素O(1)
push_back向链表尾部添加元素O(1)
pop_back移除链表尾部的元素O(N)

由于pop_back()需要找到最后一个结点的前驱结点,所以需要遍历整个链表,时间复杂度为O(N)

#ifndef CIRCLELIST_H_
#define CIRCLELIST_H_

#include <iostream>

#include <tools/base/copyable.h>
#include <tools/base/noncopyable.h>

template <typename T>
class CircleList : copyable {
public:
	struct Node : noncopyable {
		T data;
		Node *next;

		Node(T elem) : data(elem), next(nullptr){}
	};

	CircleList() : m_tail(nullptr), m_size(0){}     // 默认构造函数

	CircleList(const CircleList &other) : 
		m_tail(nullptr),
		m_size(other.size())
	{
		if(m_size == 0){
			return;
		}

		m_tail = new Node(other.tail()->data);

		Node *preNode = m_tail;
		Node *selfNode;
		Node *otherNode = other.head();
		while(otherNode != other.tail()){
			selfNode = new Node(otherNode->data);
			preNode->next = selfNode;
			preNode = preNode->next;
			otherNode = otherNode->next;
		}
		preNode->next = m_tail;
	}

	~CircleList(){
		Node *curNode = m_tail;
		Node *nextNode;
		for(int i = 0; i < m_size; ++ i){
			nextNode = curNode->next;
			delete curNode;
			curNode = nextNode;
		}
	}

	Node* tail() const {
		return m_tail;
	}

	Node* head() const {
		return m_tail->next;
	}

	int size() const {
		return m_size;
	}

	bool empty() const {
		return m_size == 0;
	}

	void push_front(T elem){                        // 向链表头部添加元素
		Node *node = new Node(elem);
		if(m_tail == nullptr){
			m_tail = node;
			m_tail->next = m_tail;
		}
		else{
			node->next = m_tail->next;
			m_tail->next = node;
		}
		
		++ m_size;
	}

	void pop_front(){                               // 移除链表头部的元素
		if(m_tail == nullptr){
			return;
		}

		if(m_size == 1){
			delete m_tail;
			m_tail = nullptr;
			-- m_size;
			return;
		}

		Node *node = m_tail->next;
		m_tail->next = node->next;
		delete node;

		-- m_size;
	}

	void push_back(T elem){                         // 向链表尾部添加元素
		Node *node = new Node(elem);
		if(m_tail == nullptr){
			m_tail = node;
			m_tail->next = m_tail;
		}
		else{
			node->next = m_tail->next;
			m_tail->next = node;
			m_tail = node;
		}

		++ m_size;
	}

	void pop_back(){                                // 移除链表尾部的元素
		if(m_tail == nullptr){
			return;
		}

		if(m_size == 1){
			delete m_tail;
			m_tail = nullptr;
			-- m_size;
			return;
		}

		Node *preNode = m_tail->next;
		while(preNode->next != m_tail){
			preNode = preNode->next;
		}
		preNode->next = m_tail->next;
		delete m_tail;
		m_tail = preNode;

		-- m_size;
	}

	void operator=(const CircleList& other){
		this->~CircleList();

		m_tail = nullptr;
		m_size = other.size();

		if(m_size == 0){
			return;
		}

		m_tail = new Node(other.tail()->data);
		Node *preNode = m_tail;
		Node *selfNode;
		Node *otherNode = other.head();
		while(otherNode != other.tail()){
			selfNode = new Node(otherNode->data);
			preNode->next = selfNode;
			preNode = selfNode;
			otherNode = otherNode->next;
		}
		preNode->next = m_tail;
	}

	// for pratice and it doesn't
	void insert(int index, T elem){}                // 向指定位置插入元素

	void remove(int index){}                        // 删除指定位置的元素

	void set(int index, T elem){}                   // 修改指定位置的元素

	T& get(int index) const {}                      // 获取指定位置的元素

	// for unit test
	void print(const char *name) const {
		std::cout << name << "(CircleList): size = " << m_size << '\n';
		std::cout << "data: ";
		if(m_size == 0){
			std::cout << "nullptr" << std::endl;
			return;
		}

		Node *node = head();
		for(int i = 0; i < m_size; ++ i){
			std::cout << node->data << " -> ";
			node = node->next;
		}
		std::cout << head()->data << std::endl;
	}

private:
	Node *m_tail;
	int m_size;
};

#endif // CIRCLELIST_H_

双向链表(List)

双向链表每个结点是由一个数据域和两个指针域(分别指向前驱结点和后继结点)构成
优点在于可以正向或者反向遍历链表,灵活性提高
缺点是每个结点多了一个指针域,多占用了存储空间(32位4字节,64位8字节)
一般情况下,双向链表都会以双向循环链表方式实现,这样可以优势最大化

核心函数如下:

函数功能时间复杂度
push_front向链表头部添加元素O(1)
pop_front移除链表头部的元素O(1)
push_back向链表尾部添加元素O(1)
pop_back移除链表尾部的元素O(1)
#ifndef LIST_H_
#define LIST_H_

#include <iostream>

#include <tools/base/copyable.h>
#include <tools/base/noncopyable.h>

template <typename T>
class List : copyable {
public:
	struct Node {
		T data;
		Node *pre;
		Node *next;

		Node(T elem) : data(elem), pre(nullptr), next(nullptr){}
	};

	List() : m_head(nullptr), m_size(0){}                           // 默认构造函数

	List(const List& other) :                                       // 拷贝构造函数
		m_head(nullptr),
		m_size(other.size())
	{
		copy(other);
	}

	~List(){
		Node *curNode = m_head;
		Node *nextNode;
		for(int i = 0; i < m_size; ++ i){
			nextNode = curNode->next;
			delete curNode;
			curNode = nextNode;
		}
	}

	Node* head() const {
		return m_head;
	}

	int size() const {
		return m_size;
	}

	bool empty() const {
		return m_size == 0;
	}

	void push_front(T elem){                                        // 向链表头部添加元素
		Node *node = new Node(elem);
		if(m_head == nullptr){
				m_head = node;
				m_head->next = m_head;
				m_head->pre = m_head;
		}
		else{
				Node *tailNode = m_head->pre;
				node->next = m_head;
				node->pre = tailNode;
				m_head->pre = node;
				tailNode->next = node;
				m_head = node;
		}
		
		++ m_size;
	}

	void pop_front(){                                               // 移除链表头部的元素
		if(m_head == nullptr){
			return;
		}

		if(m_size == 1){
			delete m_head;
			m_head = nullptr;
		}
		else{
			Node *nextNode = m_head->next;
			Node *tailNode = m_head->pre;

			tailNode->next = nextNode;
			nextNode->pre = tailNode;
			delete m_head;
			m_head = nextNode;
		}

		-- m_size;
	}

	void push_back(T elem){                                         // 向链表尾部添加元素
		Node *node = new Node(elem);
		if(m_head == nullptr){
			m_head = node;
			m_head->next = m_head;
			m_head->pre = m_head;
		}
		else{
			Node *tailNode = m_head->pre;
			node->next = m_head;
			node->pre = tailNode;
			tailNode->next = node;
			m_head->pre = node;
		}

		++ m_size;
	}

	void pop_back(){                                                // 移除链表头部的元素
		if(m_head == nullptr){
			return;
		}

		if(m_size == 1){
			delete m_head;
			m_head = nullptr;
		}
		else{
			Node *tailNode = m_head->pre;
			tailNode->pre->next = m_head;
			m_head->pre = tailNode->pre;
			delete tailNode;
		}

		-- m_size;
	}

	void operator=(const List& other){
		this->~List();
		m_head = nullptr;
		m_size = other.size();

		copy(other);
	}

	T& front() const {
		return m_head->data;
	}

	T& back() const {
		return m_head->pre->data;
	}

	// for pratice and it doesn't
	void insert(int index, T elem){}                // 向指定位置插入元素

	void remove(int index){}                        // 删除指定位置的元素

	void set(int index, T elem){}                   // 修改指定位置的元素

	T& get(int index) const {}                      // 获取指定位置的元素

	// for unit test
	void print(const char *name) const {
		std::cout << name << "(List): size = " << m_size << '\n';
		std::cout << "data: ";
		if(m_size == 0){
			std::cout << "nullptr" << std::endl;
			return;
		}

		Node *node = m_head;
		for(int i = 0; i < m_size; ++ i){
			std::cout << node->data << " <=> ";
			node = node->next;
		}
		std::cout << m_head->data << std::endl;
	}

private:
	void copy(const List& other){
		if(m_size == 0){
				return;
		}

		m_head = new Node(other.head()->data);
		Node *preNode = m_head;
		Node *selfNode;
		Node *otherNode = other.head()->next;
		for(int i = 1; i < m_size; ++ i){
			selfNode = new Node(otherNode->data);
			selfNode->pre = preNode;
			preNode->next = selfNode;
			preNode = selfNode;

			otherNode = otherNode->next;
		}

		preNode->next = m_head;
		m_head->pre = preNode;
	}

private:
	Node *m_head;
	int m_size;
};

#endif // LIST_H_
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值