数据结构中的单链表
什么是单链表?
单链表是一种线性、单向的数据结构,数据保存在节点上,每个节点通过链接连接到下一个节点。每个节点包含一个数据字段和一个指向下一个节点的链接。单链表只能在一个方向上遍历,而双链表可以双向遍历。
以下是单链表的节点结构:

为什么要使用链表而不是数组?
在某些情况下,使用链表比使用 排列. 以下是一些场景:
- 未知元素的数量: 当您不知道程序运行时需要存储多少个元素时,可以使用链表。当您将元素添加到链表时,内存是动态分配的。
- 随机访问: 在您不需要使用元素随机访问的场景中,您可以使用链接列表。
- 中间插入: 在数组中间插入元素是一项复杂的任务。因为您需要将其他元素推到右侧。但是,链接列表允许您将元素添加到您想要的任何位置。
Opera单链表
单链表适合动态分配内存。它提供了链表的所有基本操作,即插入、删除、搜索、更新、合并两个链表、遍历等。
这里我们讨论链表的以下操作:
- 插入头部
- 插入尾部
- 在节点后插入
- 在节点前插入
- 删除头节点
- 删除尾节点
- 搜索并删除节点
- 遍历链接列表
这是一个具有四个节点的链表的示例。

在单链表的头部插入
这是一个简单的操作。一般来说,它被称为推送单链表。您需要创建一个新节点并将其放在链表的头部。
要执行此操作,我们需要遵循两个重要条件。它们是
- 如果列表为空,则新创建的节点将作为头节点,头节点的下一个节点将为“NULL”。
- 如果列表不为空,则新节点将作为头节点,并且next将指向上一个头节点。
以下是在链表头部插入节点的伪代码:
function insertAtHead( head, value ): newNode = Node(value) if head is NULL: head = newNode return head else: newNode.next = head return newNode

在单链表末尾插入
在链表尾部插入节点与在链表头插入节点有相似之处。你需要做的就是,遍历到尾节点或尾节点。然后将尾节点的“下一个”节点指向新创建的节点。如果头节点为空,则新节点将为头节点。
步骤如下:
步骤1) 遍历直到当前节点的“下一个”节点为空。
步骤2) 创建具有指定值的新节点。
步骤3) 将新节点指定为尾节点的下一个节点。
在单链表尾部插入节点的伪代码:
function insertAtEnd( head, value ): newNode = Node(value) if head is NULL: head = newNode return head while head.next is not NULL: then head = head.next head.next = newNode newNode.next = NULL

在单链表的节点后插入
在节点后插入有两个部分:搜索节点并附加在搜索到的节点后。我们需要遍历所有节点。对于每个节点,我们需要与搜索元素进行匹配。如果匹配,那么我们将在搜索到的节点后添加新节点。
步骤如下:
步骤1) 遍历下一个节点,直到当前节点的值等于搜索项。
步骤2) 新节点的下一个指针将是当前节点的下一个指针。
步骤3) 当前节点的下一个节点将成为新节点。
以下是在节点后插入节点的伪代码:
function insertAfter( head, value, searchItem ): newNode = Node(value) while head.value equals searchItem: then head = head.next newNode.next = head.next.next head.next = newNode

在单链表的节点前插入
这个函数和在节点后插入很相似。我们必须创建一个新节点并遍历链表,直到当前节点与搜索节点匹配。之后,我们将新节点添加为当前节点的前一个节点。
步骤如下:
步骤1) 遍历直到下一个节点的值等于搜索项。
步骤2) 创建新节点,并将该节点的“next”赋值为当前节点的下一个节点。
步骤3) 将新节点指定为当前节点的下一个节点。
伪代码如下:
function insertBefore( head, value, searchItem ): newNode = Node(value) while head.next.value equals searchItem: then head = head.next newNode.next = head.next.next head.next = newNode

删除单链表的表头
在链表的每个函数中,头指针都是作为参数提供的。您需要删除头节点,并将头节点的下一个节点作为链表的新头。我们还需要释放已删除节点的内存。否则,当另一个程序尝试访问它时,该内存将被标记为已占用。
删除单链表头部的步骤如下:
步骤1) 将头节点的下一个节点指定为新的头。
步骤2) 释放前一个头节点分配的内存。
步骤3) 返回新的头节点。
删除头节点的伪代码:
function deleteHead( head ): temp = head head = head.next free( temp ) return head

删除单链表的尾部
删除尾节点跟删除头节点比较熟悉,区别在于需要遍历到链表末尾再删除,单链表中,下一个节点为“NULL”的节点就是尾节点。
删除链表尾节点的步骤如下:
步骤1) 遍历到尾节点之前。保存当前节点。
步骤2) 释放当前节点的下一个节点的内存。
步骤3) 将当前节点的下一个节点设置为NULL。
以下是删除尾节点的伪代码:
function deleteTail( head ): while head.next.next is not NULL: head = head.next free( head.next ) head.next = NULL

从单链表中搜索并删除节点
此函数有两个任务,搜索和删除。我们需要搜索到单链表的末尾。如果我们发现任何相似的节点,我们将删除该节点。之后,我们需要合并或链接已删除节点的左右节点。以下是执行此操作的步骤:
步骤1) 遍历直到链表末尾。检查当前节点是否等于搜索节点。
步骤2) 如果有任何节点匹配,则将节点指针存储到当前节点。
步骤3) 前一个节点的“下一个”将是当前节点的下一个节点。
步骤4) 删除并释放当前节点的内存。
从单链表中搜索和删除节点的伪代码:
function searchAndDelete( head, searchItem ): while head.next.next is not NULL and head.next.value is not equals searchItem : head = head.next head.next = head.next.next delete(head.next)

遍历单链表
单链表只具有从头到尾遍历的功能。没有指向前一个节点的指针;这就是为什么我们不能以相反的顺序遍历单链表。我们将遍历每个节点并打印当前节点的值,直到得到 null。
步骤如下:
步骤1) 遍历每个节点,直到得到 null 作为下一个节点。
步骤2) 打印当前节点的值。
遍历单链表的伪代码:
function traverse( head ): while head.next is not NULL: print the value of the head head = head.next
单链表示例 C++
#include<iostream>
using namespace std;
struct Node{
int data;
struct Node *next;
};
void insertAtHead(Node* &head, int value){
Node* newNode = new Node();
newNode->data = value;
newNode->next = NULL;
if(head!=NULL){
newNode->next = head;
}
head = newNode;
cout<<"Added "<<newNode->data<<" at the front"<<endl;
}
void insertEnd(Node* &head, int value){
if(head==NULL){
insertAtHead(head,value);
}
Node* newNode = new Node();
newNode->data = value;
newNode->next = NULL;
Node *temp = head;
while(temp->next!=NULL){
temp = temp->next;
}
temp->next = newNode;
cout<<"Added "<<newNode->data<<" at the end"<<endl;
}
void searchAndDelete(Node **headPtr, int searchItem){
Node *temp = new Node();
if( (*headPtr)->data == searchItem ){
temp = *headPtr;
*headPtr = (*headPtr)->next;
free(temp);
}else{
Node *currentNode = *headPtr;
while(currentNode->next != NULL){
if(currentNode->next->data == searchItem){
temp = currentNode->next;
currentNode->next = currentNode->next->next;
free(temp);
}else{
currentNode = currentNode->next;
}
}
}
cout<<"Deleted Node\t"<<searchItem<<endl;
return;
}
void insertAfter(Node * &headPtr, int searchItem, int value){
Node* newNode = new Node();
newNode->data = value;
newNode->next = NULL;
Node *head = headPtr;
while(head->next!=NULL && head->data!=searchItem){
head = head->next;
}
newNode->next = head->next;
head->next = newNode;
cout<<"Inserted "<<value<<" after node\t"<<searchItem<<endl;
}
void insertBefore(Node * &headPtr, int searchItem, int value){
Node* newNode = new Node();
newNode->data = value;
newNode->next = NULL;
Node *head = headPtr;
while(head->next!=NULL && head->next->data!=searchItem){
head = head->next;
}
newNode->next = head->next;
head->next = newNode;
cout<<"Inserted "<<value<<" before node\t"<<searchItem<<endl;
}
void traverse(Node *headPointer){
Node* tempNode = new Node();
tempNode = headPointer;
cout<<"Traversal from head:\t";
while(tempNode !=NULL){
cout<<tempNode->data;
if(tempNode->next)
cout<<" --> ";
tempNode = tempNode ->next;
}
cout<<endl;
}
int main(){
Node *head = NULL;
insertAtHead(head,5);
insertAtHead(head,6);
insertAtHead(head,7);
insertEnd(head,9);
traverse(head);
searchAndDelete(&head,6);
traverse(head);
insertAfter(head,7,10);
insertBefore(head,9,11);
traverse(head);
}
输出:
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Traversal from head: 7 → 6 → 5 → 9 Deleted Node 6 Traversal from head: 7 → 5 → 9 Inserted 10 after node 7 Inserted 11 before node 9 Traversal from head: 7 → 10 → 5 → 11 → 9
单链表示例 Python 教学计划
class Node:
def __init__(self,data = None, next = None):
self.data = data
self.next = next
class SinglyLinkedList:
def __init__(self):
self.head = None
def insertAtHead(self, value):
newNode = Node(data=value)
if self.head is not None:
newNode.next = self.head
self.head = newNode
print(f'Added {newNode.data} at the front.')
return
def insertAtEnd(self,value):
if self.head is None:
self.insertAtHead(value)
newNode = Node(value)
temp = self.head
while temp.next is not None:
temp = temp.next
temp.next = newNode
print(f'Added {newNode.data} at the end.')
def searchAndDelete(self,searchItem):
temp = Node()
if self.head is searchItem:
temp = self.head
self.head = self.head.next
else:
currentNode = self.head
while currentNode.next is not None:
if currentNode.next.data is searchItem:
temp = currentNode.next
currentNode.next = currentNode.next.next
else:
currentNode = currentNode.next
print(f'Deleted node\t{searchItem}')
return
def insertAfter(self,searchItem,value):
newNode = Node(data=value)
temp = self.head
while temp.next is not None and temp.data is not searchItem:
temp = temp.next
newNode.next = temp.next
temp.next = newNode
print(f'Inserted {value} after node\t{searchItem}')
return
def insertbefore(self,searchItem,value):
newNode = Node(data=value)
temp = self.head
while temp.next is not None and temp.next.data is not searchItem:
temp = temp.next
newNode.next = temp.next
temp.next = newNode
print(f'Inserted {value} before node\t{searchItem}')
return
def traverse(self):
temp = self.head
print("Traversing from head:\t",end="")
while temp:
print("{}\t".format(temp.data),end="")
temp = temp.next
print()
SinglyLinkedList = SinglyLinkedList()
SinglyLinkedList.insertAtHead(5)
SinglyLinkedList.insertAtHead(6)
SinglyLinkedList.insertAtHead(7)
SinglyLinkedList.insertAtEnd(9)
SinglyLinkedList.traverse()
SinglyLinkedList.searchAndDelete(6)
SinglyLinkedList.traverse()
SinglyLinkedList.insertAfter(7,10)
SinglyLinkedList.insertbefore(9, 11)
SinglyLinkedList.traverse()
输出:
Added 5 at the front. Added 6 at the front. Added 7 at the front. Added 9 at the end. Traversing from head: 7 6 5 9 Deleted node 6 Traversing from head: 7 5 9 Inserted 10 after node 7 Inserted 11 before node 9 Traversing from head: 7 10 5 11 9
单链表的复杂性
复杂度有两种:时间复杂度和空间复杂度。单链表的最坏情况和平均情况的时间复杂度相同。
最佳情况的时间复杂度:
- 头部插入操作可以在 O(1) 内完成。因为我们不需要遍历链表内部。
- 如果搜索元素存在于头节点中,则可以在 O(1) 内完成搜索和删除操作。
平均情况的时间复杂度:
- 如果单链表中有“n”个元素,则在链表内插入将需要 O(n)。
- 搜索和删除也可能需要 O(n),因为搜索元素可能存在于尾节点中。在这种情况下,您应该遍历整个列表。
单链表的空间复杂度
单链表动态分配内存,如果要存储 n 个元素,则需要分配 n 个内存单元,因此空间复杂度为 O(n)。
