<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">链表是c/c++中的一个重要的数据结构</span>
首先,他在物理存储上是非连续的,跟数组正好相反,数组是一系列连续的存储单元,这样的特性可能会使链表的访问时间复杂度O(n)高于数组O(1),但是对于链表的插入操作可以达到O(1),删除同理,所以当我们处理数据可能会有大量的插入或者删除操作时,用链表存放数据就非常适用啦
另外,因为链表在内存中是不连续的,所以它可以充分利用内存,同时,在内存空间足够的情况下,它的大小也是没有限制的,链表支持动态增长,而数组则是需要预先分配内存,可能会浪费掉一些内存资源(因为可能根本不会用到某些内存单元)
所以,其实链表还是相当有用滴 废话说了一些,开始码代码了
先看一下结构体的构成吧:
struct Node{
int elem;
Node *next;
Node(){
elem = 0;
next = NULL;
}
Node(int elem_){
elem = elem_;
next = NULL;
}
};
数据域只有一个int类型的数据elem,指针域为next,指向下一个节点; 默认构造函数将elem属性初始化为0,指向null,构造函数将elem属性初始化为参数elem_的值,指向的节点同样为null
首先讲一下如何创建链表
方法很简单,根据你现有的数据,创建新的Node节点,然后用next指针把链表和这些节点串联起来
Node *CreateNodeList(){
/*
func:根据指定数据创建链表
return:创建完成之后的链表的头指针
*/
int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Node *head = new Node();
if(head == NULL){
//内存分配失败
return NULL;
}else{
head->elem = data[0];
Node *temp, *currentHead;
currentHead = head;
for(int i = 1; i < 10; i++){
temp = new Node(data[i]);
currentHead->next = temp;
//指针向后移一个单元
currentHead = currentHead->next;
}
return head;
}
}
现在开始讲一下插入操作,插入操作又分为几种,一种是链表头插入操作,一种是链表尾插入操作,一种是链表中间插入操作,
表头插入很简单,直接新建一个节点,指向当前的头节点就ok了
Node *InsertAtHead(Node *head, int elem){
/*
func:在链表表头插入节点
para:head为链表头指针,elem为要插入节点的数据成员
return:插入操作完成之后的链表的头指针
*/
Node *newHead = new Node(elem);
if(NULL == head){
//这里实际上相当于新建了一个链表,表头为newHead
return newHead;
}else{
newHead->next = head;
return newHead;
}
}
表尾插入,需要将指针移动到列表尾,然后进行插入操作
Node *InsertAtTail(Node *head, int elem){
/*
func:在链表表尾插入节点
para:head为链表头指针,elem为要插入节点的数据成员
return:插入操作完成之后的链表的头指针
*/
if(NULL == head)
return NULL;
else{
Node *currentHead = head;
Node *temp;
//while循环使指针移动到链表尾
while(currentHead->next){
currentHead = currentHead->next;
}
temp = new Node(elem);
currentHead->next = temp;
return head;
}
}
一般插入,这里模拟的是在一个有序链表中插入节点,使得插入之后,链表仍然有序,这种插入可能会遇到在表头插入或者在表尾插入的情况,所以我们要单独考虑这两种情况
Node *Insert(Node *head, int elem){
/*
funct:在升序链表中插入节点,使得节点插入完成之后链表仍然是升序
para:head为链表头指针,elem为要插入节点的数据成员
return:插入操作完成之后的链表头指针
*/
if(head == NULL){
return head;
}else{
Node *currentHead = head;
Node *temp = new Node(elem);
if(elem < head->elem){
//因为已经保证按升序插入,所以这种情况下直接在表头插入
temp->next = head;
return temp;
}else{
while(currentHead->next){
int curElem = currentHead->elem;
int nextElem = currentHead->next->elem;
if(elem >= curElem && elem < nextElem){
//在节点currentHead和currentHead->next之间插入temp
temp->next = currentHead->next->next;
currentHead->next = temp;
break;
}
currentHead = currentHead->next;
}
//在表尾插入
if(currentHead->next == NULL){
currentHead->next = temp;
}
return head;
}
}
}
删除节点同样有3个版本,在链表表头删除,在链表表尾删除,和在链表中间删除
首先是在链表表头删除节点,这个操作很简单,断开当前表头和链表的连接即可
Node *DeleteAtHead(Node *head){
/*
func:删除链表表头节点
para:head为链表的头指针
return:返回新链表的头指针
*/
Node *newHead = head->next;
//head节点与链表断开
head->next = NULL;
return newHead;
}
然后是在链表表尾删除节点,同样很容易,只要将链表遍历一遍,最后断开尾节点跟前驱节点的连接即可
Node *DeleteAtTail(Node *head){
/*
func:删除链表表头节点
para:head为链表的头指针
return:返回新链表的头指针
*/
Node *currentHead = head;
//前驱节点
Node *prev;
while(currentHead->next){
prev = currentHead;
currentHead = currentHead->next;
}
//经过while循环后,currentHead现在是尾节点,prev是它的前驱节点
prev->next = NULL;
return head;
}
现在再来说一下通过数据成员匹配的删除,这种情况下的删除需要记录下删除节点的前驱节点和下一个节点,然后让这两个节点连接起来,过程如下Node *DeleteByElem(Node *head, int elem){
/*
funct:删除数据成员为elem的节点
para:head为链表头指针,elem为要删除节点的数据成员值
return:删除操作完成之后的链表头指针
*/
Node *currentHead = head;
if(currentHead->elem == elem){
head = head->next;
currentHead->next = NULL;
return head;
}else{
//prev用来记录当前的节点的前驱节点
Node *prev = head;
while(currentHead){
if(currentHead->elem == elem){
prev->next = currentHead->next;
currentHead->next = NULL;
break;
}
prev = currentHead;
currentHead = currentHead->next;
}
return head;
}
}
最后就是稍微复杂一点的链表反转操作,我的做法是这样的,每次让当前表的表头与剩下节点断开,然后插入到反转链表的表头,这样就完成了链表的反转
Node *RevearseNodeList(Node *head){
/*
funct:反转链表
para:head为链表头指针
return:反转操作完成之后的链表头指针
*/
if(head == NULL || head->next == NULL){
return head;
}else{
Node *currentHead = head->next;
Node *temp;
head->next = NULL;
while(currentHead){
//当前表的表头指向反转表的表头
temp = currentHead->next;
currentHead->next = head;
//head移动到指向反转表的表头
head = currentHead;
currentHead = temp;
}
return head;
}
}
好了,差不多就是这些了,不过最近看到网上很多人对于表头,表尾是否为空有一些疑问,其实表头跟表尾为空或者不为空都是可以的,我这里的实现,表头,表尾都用来存放数据
上面的代码都是经过调试的,如果有什么bug希望大家给我指出来,一起学习,一起进步~~