单向链表(概念篇+代码)

一、单向链表的概念
二、单向链表的元素插入
三、单向链表的元素删除
四、单向链表的元素查找
五、单向链表的元素索引
六、单向链表的元素修改
七、顺序表完整可运行源码

一、单向链表的概念

1、单链表的概念

对于顺序存储的结构,最大的缺点就是:插入删除的时候需要移动大量的元素,所以基于前人的智慧,他们发明了链表。
链表是由一个个结点组成,每个结点之间通过链接关系串联起来,每个结点都有一个后继结点,最后一个结点的后继结点为空结点,如图所示:
在这里插入图片描述

由链接关系A->B组织起来的两个结点,B被称为A的后继结点,A被称为B的前驱结点。链表分为单向链表、双向链表、循环链表等等。本文只介绍单向链表。
一个链表结点由两部分组成:数据域指针域。数据可以是任意类型,由编码的人自行指定。指针域指向后继结点的地址。一个结点包含的两部分如下图所示:
在这里插入图片描述

2、整个单链表的「类与节点定义 + 析构函数」

为了实现下面函数的所有操作,我们先定义链表的基础结构。以下是不带头结点单链表的完整类声明与析构函数实现:

typedef int eleType;

struct ListNode {
	eleType data;
	ListNode* next;
	// 构造函数
	ListNode(eleType x) : data(x), next(NULL) { }
};

class LinkedList {
private:
	ListNode* head;
	int size;
public:
	LinkedList() : head(NULL) , size(0) { }  // 构造函数
	~LinkedList();	// 析构函数
	void insert(int i, eleType value);  // 插入
	void remove(int i); // 删除
	ListNode* find(eleType value);  // 查找(按值)
	ListNode* get(int i);  // 取第i个节点(按索引查找)
	void updata(int i, eleType value); // 修改
	void print();	// 遍历
};

// 析构函数
LinkedList::~LinkedList() {
	ListNode* curr = head;
	while (curr) {
		ListNode* temp = curr;
		curr = curr->next;
		delete temp;
	}
}

二、单向链表的元素插入

1、元素插入的概念

单向链表的元素插入,就是指给定一个索引i和一个元素data,生成一个值为data的结点,并且插入到第i个位置上。

2、元素插入图解

本次插入操作,是给定一个数值5,要求插入到单向链表的索引为4的位置上。
p(即pre)代表目前正在遍历的结点,当计数到3的时候,p的后继结点a(即aft)也找到了,然后生成值为5的结点vtx,将p的后继指向vtx,将vtx的后继指向a。
在这里插入图片描述

3、元素插入的步骤

第1步、判断插入位置是否合法,如果不合法则抛出异常(比如:原本只有5个元素,给定的索引是100,那显然这个位置是不合法的)。
第2步、对给定的元素,生成一个链表结点。
第3步、如果插入位置是0,则直接把生成的结点的后继结点,设置为当前的链表头结点,并且把生成的结点设置为新的链表头。
第4步、如果插入位置不是0,则遍历到插入位置的前一个位置,把生成的结点插入进来。
第5步、更新链表的大小,即对链表的大小执行加一操作。

4、伪代码实现

// 插入
void LinkedList::insert(int i, eleType value) {
	// 判断查如位置合法化 [0,size]
	if (i < 0 || i > size) {
		throw std::out_of_range("Invaild position");
	}
	// 用ListNode中的构造函数创建新节点
	ListNode* newNode = new ListNode(value);
	// 创建的是没有头结点的链表,当插入位置为0时,直接修改head首节点
	if (i == 0) {
		newNode->next = head;
		head = newNode;
	}
	else { // 其他合法位置
		ListNode* curr = head;
		for (int j = 0; j < i - 1; j++) { //
			curr = curr->next;
		}
		newNode->next = curr->next;
		curr->next = newNode;
	}
	size++;
}

三、单向链表的元素删除

1、元素删除的概念

单向链表的元素删除,就是指给定一个索引i,将从链表头开始数到的第i个结点删除。

2、元素删除的图解

要求删除索引为4的链表结点,从前往后遍历链表,当遍历到索引为3的链表结点,则将它的后继结点存储到del 中,并且将它的后继指向它后继的后继。
在这里插入图片描述

3、元素删除的步骤

第1步、判断删除位置是否合法,如果不合法则抛出异常。
第2步、如果删除位置为首个结点,直接把链表头更新为它的后继结点。
第3步、如果删除位置非首个结点,则遍历到要删除位置的前一个结点,并且把前一个结点的后继结点设置为它后继的后继。
第4步、更新链表的大小,也就是将链表的大小执行减一操作。

4、伪代码实现

// 删除
void LinkedList::remove(int i) {
	// 判断删除位置合法化 [1,size-1]
	if (i < 0 || i >= size) {
		throw std::out_of_range("Invaild position");
	}
	// 删除首节点
	if (i == 0) {
		ListNode* curr = head;
		head = curr->next;
		delete curr;
	}
	else { // 删除其他合法节点
		ListNode* curr = head;
		for (int j = 0; j < i - 1; j++) { // 找前驱
			curr = curr->next;
		}
		ListNode* tmp = curr->next;
		curr->next = tmp->next;
		delete tmp;
	}
	size--;
}

四、单向链表的元素查找

1、元素查找的概念

单向链表的元素查找,是指在链表中查找指定元素x是否存在,如果存在则返回该结点,否则返回NULL。由于需要遍历整个链表进行元素对比,所以查找的时间复杂度为0(n)。

2、元素查找的图解

如图所示,要求查找值为8的结点,从链表头结点开始遍历,直到遍历到值为8到结点以后,返回这个结点。
在这里插入图片描述

3、元素查找的步骤

第1步、遍历整个链表,对链表中的每个元素,和指定元素进行比较,如果相等则返回当前遍历到的结点
;第2步、如果遍历完整个链表,都没有找到相等的元素,则返回NULL;

4、伪代码实现

// 查找(按值)
ListNode* LinkedList::find(eleType value){
	ListNode* curr = head;
	while(curr && curr->data != value){
		curr = curr->next;
	}
	return curr;
}

五、单向链表的元素索引

1、元素索引的概念

单向链表的元素索引,是指给定一个索引值i,从链表头结点开始数,数到第i个结点并且返回它,时间复杂度0(n)。

2、元素索引的图解

给定的索引值是5,tmp代表当前遍历到的结点,记录一个变量j,j自增的过程,判断是否和5相等,如果相等则代表找到对应的结点,直接返回图中值为8的结点。
在这里插入图片描述

3、元素索引的步骤

第1步、首先判断给定的索引是否合法,不合法就抛出异常;
第2步、直接通过索引访问即可获得对应的元素;

4、伪代码实现

// 取第i个节点(按索引查找)
ListNode* LinkedList::get(int i) {
	// 判断取节点位置合法
	if (i < 0 || i >= size) {
		throw std::out_of_range("Invaild position");
	}
	ListNode* curr = head;
	for (int j = 0; j < i; j++) { // 找目标本身
		curr = curr->next;
	}
	return curr;
}

六、单向链表的元素修改

1、元素修改的概念

单向链表的元素修改是指将链表中指定索引的元素更新为新的值。

2、元素索引的步骤

直接通过索引访问即可获得对应的节点,修改成指定的值;

3、伪代码实现

// 修改
void LinkedList::updata(int i, eleType value) {
	get(i)->data = value;
}

七、单链表完整可运行源码

#include<iostream>
#include<stdexcept>
using namespace std;

typedef int eleType;

struct ListNode {
	eleType data;
	ListNode* next;
	// 构造函数
	ListNode(eleType x) : data(x), next(NULL) { }
};

class LinkedList {
private:
	ListNode* head;
	int size;
public:
	LinkedList() : head(NULL) , size(0) { }  // 构造函数
	~LinkedList();	// 析构函数
	void insert(int i, eleType value);  // 插入
	void remove(int i); // 删除
	ListNode* find(eleType value);  // 查找(按值)
	ListNode* get(int i);  // 取第i个节点(按索引查找)
	void updata(int i, eleType value); // 修改
	void print();	// 遍历
};

// 析构函数
LinkedList::~LinkedList() {
	ListNode* curr = head;
	while (curr) {
		ListNode* temp = curr;
		curr = curr->next;
		delete temp;
	}
}

// 插入
void LinkedList::insert(int i, eleType value) {
	// 判断查如位置合法化 [0,size]
	if (i < 0 || i > size) {
		throw std::out_of_range("Invaild position");
	}
	// 用ListNode中的构造函数创建新节点
	ListNode* newNode = new ListNode(value);
	// 创建的是没有头结点的链表,当插入位置为0时,直接修改head首节点
	if (i == 0) {
		newNode->next = head;
		head = newNode;
	}
	else { // 其他合法位置
		ListNode* curr = head;
		for (int j = 0; j < i - 1; j++) { //
			curr = curr->next;
		}
		newNode->next = curr->next;
		curr->next = newNode;
	}
	size++;
}

// 删除
void LinkedList::remove(int i) {
	// 判断删除位置合法化 [1,size-1]
	if (i < 0 || i >= size) {
		throw std::out_of_range("Invaild position");
	}
	// 删除首节点
	if (i == 0) {
		ListNode* curr = head;
		head = curr->next;
		delete curr;
	}
	else { // 删除其他合法节点
		ListNode* curr = head;
		for (int j = 0; j < i - 1; j++) { // 找前驱
			curr = curr->next;
		}
		ListNode* tmp = curr->next;
		curr->next = tmp->next;
		delete tmp;
	}
	size--;
}

// 查找(按值)
ListNode* LinkedList::find(eleType value) {
	ListNode* curr = head;
	while (curr && curr->data != value) { //
		curr = curr->next;
	}
	return curr;
}

// 取第i个节点(按索引查找)
ListNode* LinkedList::get(int i) {
	// 判断取节点位置合法
	if (i < 0 || i >= size) {
		throw std::out_of_range("Invaild position");
	}
	ListNode* curr = head;
	for (int j = 0; j < i; j++) { // 找目标本身
		curr = curr->next;
	}
	return curr;
}


// 修改
void LinkedList::updata(int i, eleType value) {
	get(i)->data = value;
}


// 打印
void LinkedList::print() {
	ListNode* curr = head;
	for (int i = 0; i < size; i++) {
		cout << curr->data << " ";
		curr = curr->next;
	}
	cout << endl;
}


int main()
{
	LinkedList list;
	cout << "1. 插入5个元素: ";
	list.insert(0, 10);
	list.insert(1, 20);
	list.insert(2, 30);
	list.insert(3, 40);
	list.insert(4, 50);
	list.print();

	cout << "2. 删除索引为2的元素: ";
	list.remove(2);
	list.print();

	cout << "3. 查找(按值): ";
	ListNode* curr1 = list.find(10);
	cout << curr1->data << "\n";

	cout << "4. 取第4个节点的值: ";
	ListNode* curr2 = list.get(3);
	cout << curr2->data << endl;

	cout << "5. 修改链表索引为0的值: ";
	list.updata(0, 1314);
	list.print();

	return 0;
}

更多推荐