链表&算法 Implemented with C++

单链表

所谓单链表,就是线性表的链式存储——相对于顺序存储来说,链式存储不要求数据元素存储在物理位置连续的内存上,而是通过指针指向下一个元素的地址,这就是链表,通常具有这样的结构:

typedef struct Node{
	ElementType e;
	Node* next;
	Node(ElementType e_):e(e_){}
}Node;

在C++中,我们通常会封装成一个类:

class LinkedList{
	
public:
	typedef struct Node{
		ElementType e;
		Node* next;
		Node(ElementType e_):e(e_)()
	}Node;
private:
	int length = 0;
	Node* head;// Node* head = new Node(0); with header
	// Node* tail; 
public:
	//...
};

在内存方面:

  • 链表可以解决连续存储需要一次分配大量存储空间,并且不能保证内存利用率的缺点
  • 但是额外的指针域也会占用一定的内存,造成一定程度的浪费

在操作上:

  • 链表更好的支持频繁的插入删除操作
  • 但是通过指针维持元素之间的关系使得链表只能顺序访问(遍历),不能像连续存储那样直接访问

通常我们会有一个head指针——头指针来指向单链表的第一个数据元素。此外,我们还可能在第一个结点之前附加一个头结点,通常不存储信息,此时head指针指向的就是这个头结点,可以方便我们的操作:

  • 头结点的引入使得我们对第一个数据元素的操作能够和其他结点的操作保持一致,不需要单独进行处理
  • 统一了空表和非空表的处理(因为即使是空表,依然有一个头结点的存在)

Basic Operation 基本操作

Create 创建链表

有两种建立链表的方式

  • 头插法:每次插入都将新的结点插入到头指针
  • 尾插法:需要一个尾指针,每次都将新的结点插入到尾指针所指向的结点后面的位置
Insert 插入

根据所给的位置插入结点:

bool insert(int pos, ElementType e){
	// check if position out of length
	if(pos > this->length || pos < 0){
		std::cout << "Invalid position to insert" << std::endl;
		return false;
	}
	// check if list is full
	if(this->length > MAX_SIZE){
		std::cout << "Full list, position isn't available" << std::endl;
		return false;
	}
	Node* newNode = new Node(e);
	/* without header
	if(pos == 0){
		if(head != NULL){
			newNode->next = head;
		}
		head = newNode;
		return true;
	}
	*/
	int i = 0;
	Node* p = this->head;
	while(i < pos){
		p = p->next;
		i++;
	}
	newNode->next = p->next;
	p->next = newNode;
	return true;
}
Delete 删除

按位置删除:

void delet(int pos){
	// check if position out of length
	if(pos > this->length - 1 || pos < 0){
		std::cout << "Invalid position to delete" << std::endl;
		return false;
	}
	// check if list is empty
	if(this->length == 0){
		std::cout << "Empty list, no element able to delete" << std::endl;
		return false;
	}
	/* without header
	if(pos == 0){
		Node* q = head;
		head = head->next;
		delete q;
		return true;
	}
	*/
	int i = 0;
	Node* p = this->head;
	while(i < pos){
		p = p->next;
		i++;
	}
	Node* q = p->next;
	p->next = p->next->next;
	delete q;
	return true;
}

按值删除:

void delet(ElementType e){
	// check if list is empty
	if(this->length == 0){
		std::cout << "Empty list, no element able to delete" << std::endl;
		return false;
	}
	/* without header
	if(head->e == e){
		Node* q = head;
		head = head->next;
		delete q;
		return true;
	}
	*/
	int i = 0;
	Node* p = this->head;
	while(p->next != NULL){
		if(p->next->e == e){
			Node* q = p->next;
			p->next = p->next->next;
			delete q;
			return true;
		}
		p = p->next;
		i++;
	}
	std::cout << "No such element" std::endl;
	return false;
}
Find

按序号查找:

ElementType find(int pos){
	// check if position out of length
	if(pos > this->length - 1 || pos < 0){
		std::cout << "Invalid position" << std::endl;
		return -1;
	}
	// check if list is empty
	if(this->length == 0){
		std::cout << "Empty list, no element able to find" << std::endl;
		return -1;
	}
	Node* p = this->head;
	int i = 0; // int i = -1 with header
	while(i < pos){
		p = p->next;
		i++;
	}
	return p->e;
}

按值查找:

int find(ElementType e){
	// check if list is empty
	if(this->length == 0){
		std::cout << "Empty list, no element able to find" << std::endl;
		return -1;
	}
	Node* p = this->head;
	int i = 0; // int i = -1; with header
	while(p != NULL){
		if(p->e == e){
			return i;
		}
		p = p->next;
		i++;
	}
	return -1;
}

Doubly Linked List 双链表

有一个指针指向下一个结点,还有一个指针指向前面一个结点,这样的链表就是双向链表。双链表使得我们可以在某个位置的结点既可以方便的访问其后继结点,又可以访问其前驱结点:

typedef struct Node{
	ElementType e;
	Node* next;
	Node* prev;
	Node(ElementType e_):e(e_){}
}

Insert 双链表的插入

插入一个新的结点,需要让这个结点指向插入位置的上一个结点以及下一个结点:

bool insert(ElementType e, int pos){
	if(pos > this->length || pos < 0){
		std::cout << "Invalid position to insert" << std::endl;
		return false;
	}
	if(this->length >= MAX_SIZE){
		std::cout << "Full list, no position able to insert" << std::endl;
		return false;
	}
	Node* newNode = Node(e);
	if(pos == 0){
		if(this->head != NULL){
			newNode->next = head;
			head->prev = newNode;
		}
		this->head = newNode;
	}
	int i = 1;
	Node* p = this->head;
	while(i < pos){
		p = p->next;
		i++;
	}
	newNode->next = p->next;
	newNode->prev = p;
	if(p->next != NULL){
		p->next->prev = newNode;
	}
	p->next = newNode;
	return true;
}

Delete 双链表的删除

按位置删除:

bool delet(int pos){
	if(pos > this->length - 1 || pos < 0){
		std::cout << "Invalid position to insert" << std::endl;
		return false;
	}
	if(this->length == 0){
		std::cout << "Empty list, no element able to delete" << std::endl;
		return false;
	}
	int i = 0;
	Node* p = this->head;
	while(i < pos){
		p = p->next;
		i++;
	}
	Node* temp = p;
	if(p->prev != NULL){
		p->prev->next = p->next;
	}
	if(p->next != NULL){
		p->next->prev = p->prev;
	}
	if(pos == 0){
		this->head = this->head->next;
	}
	delete temp;
	return true;
}

按值删除:

bool delet(ElementType e){
	if(this->length == 0){
		std::cout << "Empty list, no element able to delete" << std::endl;
		return false;
	}
	int i = 0;
	Node* p = this->head;
	while(i < pos){
		if(p->e == e){
			Node* temp = p;
			if(p->prev != NULL){
				p->prev->next = p->next;
			}
			if(p->next != NULL){
				p->next->prev = p->prev;
			}
			if(pos == 0){
				this->head = this->head->next;
			}
			delete temp;
			return true;
		}
		p = p->next;
		i++;
	}
	std::cout << "No such element able to delete" << std::endl;
	return false;
}

Cycle Linked List 循环链表

和单链表唯一的区别就是:循环链表的尾指针指向的不是 NULL 而是头结点。正因为是循环链表,所以在每一个位置的插入和删除操作都是等价的(除非链表为空,否则不会存在有空结点的情况)。同样对于循环双链表也是相同的,只不过还需要头结点的 prev 指针指向尾结点。

Static Linked List 静态链表

静态链表是通过数组——及连续存储的方式来描述链式存储的结构:

typedef struct Node{
	ElementType e;
	int next;
	Node(ElementType e_):e(e_){}
}

可以看到,结点依然有一个 next 指针,只不过指针指向的不是地址,而是对应的数组下标。事实上,next指向了数组首地址加上 next * sizeof(ElementType) 所在的地址,因为是连续存储,所以我们可以通过数组直接访问的方式直接得到下一个结点的位置。这样,我们即利用了连续存储的优点,又结合了链式存储操作方便的好处。
不过,使用静态链表的时候,还需要我们手动维护一张空链表,这张链表存储的是当前数组中空闲的位置,当需要插入一个结点时,将空链表的表头的结点插入到非空表的相应位置,然后将空表表头的下一个结点(一个数组元素)作为空链表的新表头。

Some Algorithm of Linked List 链表的一些算法

Reverse 逆置

将带头结点的单链表就地逆置(空间复杂度为 O ( 1 ) O(1) O(1)):

void reverse(){
	if(this->length <= 1){
		return;
	}
	// find the middle node of the linked list
	int offset = this->length%2;
	Node* p = this->head;
	for(int i = 0;i <= this->length / 2 + offset;i++){
		p = p->next;
	}
	for(int j = this->length / 2 - 1;j >= 0;j--){
		Node* q = find(j);
		ElementType temp = 	q->e;
		q->e = p->e;
		p->e = temp;
		p = p->next;
	}
	return;
}

题型例如:简单的链表逆置算法

Find All Overlap Nodes 找到两条链表的公共结点

显然,有一个公共结点,那么这个公共结点之后的结点全部是公共结点,则公共部分等长,所以只需要跳过多出部分,再依次比较即可:

bool findOverlap(LinkedList l1, LinkedList l2){
	if(l1.length == 0 || l2.length == 0){
		return false;
	}
	int overlapLength = 0;
	Node* start1 = l1.head->next, start2 = l2.head->next; // if with header
	if(l1.length > l2.length){
		overlapLength = l2.length;
		for(int i = 0;i < l1.length - l2.length;i++){
			start1 = start1->next;
		}
	}
	else if(l1.length < l2.length){
		overlapLength = l1.length;
		for(int i = 0;i < l2.length - l1.length;i++){
			start2 = start2->next;
		}
	}
	for(int i = 0;i < overlapLength;i++){
		if(start1 == start2){
			return true;
		}
		start1 = start1->next;
		start2 = start2->next;
	}
	return false;
}

题型例如:找到单链表存储的单词的公共后缀

Split 分解链表

By Partiality 根据奇偶性

即,将带头结点链表 C = { a 1 , b 1 , a 2 , b 2 , … , a n , b n } C=\{a_{1},b_{1},a_{2},b_{2},\dots,a_{n},b_{n}\} C={a1,b1,a2,b2,,an,bn} 分解为两个链表 A = { a 1 , a 2 , … , a n } A=\{a_{1},a_{2},\dots,a_{n}\} A={a1,a2,,an} B = { b 1 , b 2 , … , b n } B=\{b_{1},b_{2},\dots,b_{n}\} B={b1,b2,,bn}
算法描述:

  • 分别为 A , B A,B A,B 建立一个头结点,声明一个变量 c o u n t = 0 count = 0 count=0
  • 遍历 C C C ,每遍历一个结点,将 c o u n t count count 加一,根据这个变量的奇偶性分别将结点用尾插法插入到 A , B A,B A,B链中
void splitByPartiality(LinkedList C, LinkedList A, LinkedList B){
	if(C.length == 0){
		return;
	}
	Node* p = C.head->next, *q = A.head, *r = B.head;
	for(int i = 1;i < C.length;i++){
		if(i%2){
			q->next = p;
			q = q->next;
		}
		else{
			r->next = p;
			r = r->next;
		}
		p = p->next;
	}
	C.head->next = NULL;
	return;
}

Merge 归并链表

合并两个有序链表成为一个新的有序链表,例如升序:

LinkedList merge(LinkedList l1, LinkedList l2){
	if(l1.length == 0){
		return l2;
	else if(l2.length == 0){
		return l1;
	}
	LinkedList newList;
	Node* p = l1.head->next, *q = l2.head->next, *r = newList.head;
	while(p != NULL && q != NULL){
		if(p->e >= q->e){
			r->next = q;
			q = q->next;
		}
		else{
			r->next = p;
			p = p->next;
		}
		r = r->next;
	}
	while(p != NULL){
		r->next = p;
		p = p->next;
	}
	while(q != NULL){
		r->next = q;
		q = q->next;
	}
	l1.head->next = NULL;
	l2.head->next = NULL;
	return newList;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值