一、释意
1.链表实质就是顺序表的链式表示。相比于顺序表有一些不同。顺序表是用一片地址连续的空间进行存储的 因此具有随机存储的特征(时间复杂度为O(1)),但是链表不同,链表不需要一片地址连续的空间 而是通过链(指针)来将所有元素串起来的。顺序表具有随机存储 但是插入、删除原素需要移动大量元素 而链表就是为了改善增删而定的 但是同时也失去了具有随机存取的特征,并且还需要有一个指针域(存储空间就增大了)。当访问某个元素时 只有通过链依次查找 具体看图例。
单链表的结点结构
其中data存放数据(a1...an)next存放下一个元素的地址
顺序表: 链表:
2.通常用头指针来标识一个单链表,头指针为NULL时表示此头指针指向的表示空表。并且通常每个链表都可以定义一个头结点(方便操作),头指针指向头结点。头结点当然也有数据域和指针域,但是头结点的数据域不存放元素 一般用于存放表长等信息,而头结点的指针域就存放第一个元素的地址。 如果所示:
引入头结点可以带来的优点:
1.由于第一个数据结点的位置被存放于头结点的指针域中,因此在链表的第一个位置上的操作和在表的其它位置上的操作一致,无需进行特殊处理
2.无论链表是否为空头指针总是指向头结点的非空指针(此时判定空表的方法是头结点指针是否为空)因此空表和非空表的处理也得到了统一
二、基本操作
1.单链表定义(结构体定义)
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
2.使用头插法建立单链表(含头结点)
时间复杂度为o(n)
//使用头插法建立单链表 (含头结点)
LinkList List_HeadInsert (LinkList &L){ //逆向建立单链表
LNode *s;int x;
L=(LinkList)malloc(sizeof(LNode)); //创建头结点 ;
L->next=NULL;
scanf("%d",&x); //输入第一个结点的值
while(x!=9999){ //输入9999表示结束
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
图例:采用头插法建立单链表是每次在头结点之后插入新的结点,因此读入数据的顺序与生成的链表中的元素顺序是相反的。但是它常用于链表逆置
3.使用尾插法建立单链表
时间复杂度为o(n)
//使用尾插法建立单链表(含头结点)
LinkList List_TailInsert (LinkList &L){ //逆向建立单链表
L=(LinkList)malloc(sizeof(LNode)); //创建头结点 ;
LNode *s,*r=L;int x; //s指向新节点 r指向尾结点
L->next=NULL;
scanf("%d",&x); //输入第一个结点的值
while(x!=9999){ //输入9999表示结束
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL; //尾结点置空
return L;
}
图例:采用尾插法建立单链表可以保证生成链表的结点值顺序和输入顺序一致,但是必须开辟一个尾指针永远指向尾结点(方便插入)
4.查找结点(按值查找)
时间复杂度为O(n)
查找方式:从单链表中的第一个结点出发,通过while循环依次查找下一个结点的值是否是目标值,如果是则退出循环并且返回此节点的指针,如果不是继续查找。若未找到则返回NULL;
//按值查找
LNode *LocElement(LinkList &L,int value){
LNode *p=L->next;
while(p!=NULL&&p->data!=value){
p=p->next; //未找到目标值的结点则一直往下找
}
return p; //找到了 返回此节点指针
}
5.查找节点(按序查找)
时间复杂度为O(n)
查找方式:从单链表中的第一个结点出发,通过while循环依次查找下一个结点知道找到第i个结点为止,找到了则退出循环并且返回此节点的指针,如果不是继续查找。若未找到则返NULL;
//按位序查找
LNode *GetElement(LinkList &L,int i){
int j; //用于计数
if(i<1){
return NULL; //判断i值是否合法
}
LNode *p=L->next;
while(p!=NULL&&j<i){
p=p->next;
j++; //未找到目标值的结点则一直往下找
}
return p; //找到了 返回此节点指针
}
6.单链表插入(图例)
插入结点操作是将值为value的元素插入到指定单链表的位序i处。算法先测试输入的新节点位序是否合法,如果合法则找到此位序的前一个结点进行后插。见图例,首先查到到序号为i-1的结点p,然后令新节点s的指针域指向p的后继节点,再令结点p的指针域指向新插入的结点s即可。
7.单链表按位序插入(带头结点 ,i表示元素位序)
时间复杂度为O(n)
//单链表插入(按位序插入 带头结点) i表示元素位序
bool ListInsert(LinkList &L,int i,int e){
if(i<1){ //判断输入i值是否合法
return false;
}
LNode *p; //用p来指向每个结点 L就是头结点
p=L;
int j=0;
while(p!=NULL && j<i-1){ //循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL){ //判断p指向的是否是空值
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode)); //创建要插入的结点
s->data=e; //这里可以直接
s->next=p->next;
p->next=s;
return true;
}
8.单链表按位序插入(不带头结点 ,i表示元素位序)
时间复杂度为O(n)
//单链表插入(按位序插入 不带头结点) i表示元素位序 不带头结点:L永远指向最后一个结点
bool ListInsert_NoHeadLNode(LinkList &L,int i,int e){
if(i<1){ //判断输入i值是否合法
return false;
}
if(i==1){ //当是第一个结点时需要单独考虑 因为它无next 无法插入
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s; //头指针指向新节点
}
if(i>1){
LNode *p; //让p指向第一个节点 让p去扫描
p=L;
int j=1;
while(p!=NULL && j<i-1){ //找到第i-1个结点 循环次数和带头结点的不一样哦 少一次
p=p->next;
j++;
}
if(p==NULL){ //i值不合法
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
}
return true;
}
9.给定指定节点的后插操作
时间复杂度为O(n)
//给定指定节点的后插操作
bool InsertNextNode(LNode *p,int e){
if(p==NULL){
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
if(s==NULL) { //内存分配失败
return false;
}
s->data=e;
s->next=p->next;
p->next=s;
}
10.指定节点的前插操作
时间复杂度为O(n)
这个算法很巧妙,其实质就是后插操作之后 再将两个结点的data值互换,即实现前插。(链表的作用是什么呢不就是存储数据吗?依靠存储数据而生 失去数据而亡)
//指定节点的前插操作 6
bool InsertPriorNode(LNode *p,int e){
if(p==NULL){
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
if(s==NULL){ //内存不足
return false;
}
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
11.删除结点操作
时间复杂度为O(n)
删除结点操作就是将链表中位序为i的结点删除。同单链表的插入相同有含头结点和不含头结点的,这里按带头结点的展开。也同单链表的插入一样 需要先找到位序为i-1的结点 对它进行施法图例:
其实质就是将i-1元素的指针域直接指向i+1的元素就把位序为i的元素删掉了。但是为了不浪费存储空间也需要用一个指针指向被删元素并将它free(释放)掉
12.删除结点返回被删除结点的值(按位序删)
时间复杂度为O(n)
//按位序删除(带头节点)
bool ListDelete(LinkList &L,int i,int &e){
if(i<1){
return false;
}
int j=0;
LNode *p; //用来指向被删除元素的前一个
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
if(p==NULL){
return false;
}
if(p->next==NULL){
return false;
}
e=p->next->data; //取到被删元素的值
LNode *deletedNode; //用来指向被删除元素 因为需要free()
deletedNode=p->next;
p->next=deletedNode->next;
free(deletedNode);
return true;
}
同指定结点的前插操作一样,删除结点p的操作可以用删除p的后继结点操作实现。实质就是将后继结点的值赋予自身 然后删除后继结点。
13.求单链表表长操作(含头结点)
时间复杂度为O(n)
求表长操作就是计算单链表中的结点数(表长不含头结点!!!)从第一个结点开始顺序遍历每个结点。设置一个计数器 访问完一个结点(若此结点不空)计数器自增
//求表长操作(带头结点)
int ListLength(LinkList &L){
int i=0,j; //i是计数器 j用于循环
LNode *p=L->next; //p指向第一个结点 (头结点不算入表长)
while(p!=NULL){
i++;
p=p->next;
}
return i;
}
三、完整代码
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode{ //struct相当于java里的对象 使用typedef后struct LNode 类型可表示为LNode类型
int data;
struct LNode *next; //指针类型是 LNode 指向下一个整体
}LNode,*LinkList; //LinkList类型等价于 LNode*类型; 使用不同的代表不同含义 创建结点用LNode
//使用头插法建立单链表 (含头结点)
LinkList List_HeadInsert (LinkList &L){ //逆向建立单链表
LNode *s;int x;
L=(LinkList)malloc(sizeof(LNode)); //创建头结点 ;
L->next=NULL;
scanf("%d",&x); //输入第一个结点的值
while(x!=9999){ //输入9999表示结束
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
//使用尾插法建立单链表(含头结点)
LinkList List_TailInsert (LinkList &L){ //逆向建立单链表
L=(LinkList)malloc(sizeof(LNode)); //创建头结点 ;
LNode *s,*r=L;int x; //s指向新节点 r指向尾结点
L->next=NULL;
printf("依次输入结点值 输入9999表示结束");
scanf("%d",&x); //输入第一个结点的值
while(x!=9999){ //输入9999表示结束
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL; //尾结点置空
return L;
}
//按值查找
LNode *LocElement(LinkList &L,int value){
LNode *p=L->next;
while(p!=NULL&&p->data!=value){
p=p->next; //未找到目标值的结点则一直往下找
}
return p; //找到了 返回此节点指针
}
//按位序查找
LNode *GetElement(LinkList &L,int i){
int j; //用于计数
if(i<1){
return NULL; //判断i值是否合法
}
LNode *p=L->next;
while(p!=NULL&&j<i){
p=p->next;
j++; //未找到目标值的结点则一直往下找
}
return p; //找到了 返回此节点指针
}
//单链表插入(按位序插入 带头结点) i表示元素位序
bool ListInsert(LinkList &L,int i,int e){
if(i<1){ //判断输入i值是否合法
return false;
}
LNode *p; //用p来指向每个结点 L就是头结点
p=L;
int j=0;
while(p!=NULL && j<i-1){ //循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL){ //判断p指向的是否是空值
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode)); //创建要插入的结点
s->data=e; //这里可以直接
s->next=p->next;
p->next=s;
return true;
}
//单链表插入(按位序插入 不带头结点) i表示元素位序 不带头结点:L永远指向最后一个结点
bool ListInsert_NoHeadLNode(LinkList &L,int i,int e){
if(i<1){ //判断输入i值是否合法
return false;
}
if(i==1){ //当是第一个结点时需要单独考虑 因为它无next 无法插入
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s; //头指针指向新节点
}
if(i>1){
LNode *p; //让p指向第一个节点 让p去扫描
p=L;
int j=1;
while(p!=NULL && j<i-1){ //找到第i-1个结点 循环次数和带头结点的不一样哦 少一次
p=p->next;
j++;
}
if(p==NULL){ //i值不合法
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
}
return true;
}
//给定指定节点的后插操作
bool InsertNextNode(LNode *p,int e){
if(p==NULL){
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
if(s==NULL) { //内存分配失败
return false;
}
s->data=e;
s->next=p->next;
p->next=s;
}
//指定节点的前插操作 6
bool InsertPriorNode(LNode *p,int e){
if(p==NULL){
return false;
}
LNode *s=(LNode*)malloc(sizeof(LNode));
if(s==NULL){ //内存不足
return false;
}
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
//按位序删除(带头节点)
bool ListDelete(LinkList &L,int i,int &e){
if(i<1){
return false;
}
int j=0;
LNode *p=L; //用来指向被删除元素的前一个
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
if(p==NULL){
return false;
}
if(p->next==NULL){
return false;
}
e=p->next->data; //取到被删元素的值
LNode *deletedNode; //用来指向被删除元素 因为需要free()
deletedNode=p->next;
p->next=deletedNode->next;
free(deletedNode);
return true;
}
//求表长操作(带头结点)
int ListLength(LinkList &L){
int i=0,j; //i是计数器 j用于循环
LNode *p=L->next; //p指向第一个结点 (头结点不算入表长)
while(p!=NULL){
i++;
p=p->next;
}
return i;
}
//打印链表(带头结点)
void PrintList(LinkList &L){
int i;
int j=ListLength(L);
LNode *p=L->next;
for(i=0;i<j;i++){
printf("%d\n",p->data);
p=p->next;
}
}
//test
int main(){
int e;
LinkList L;
List_TailInsert(L); //输入9999表示结束
int j=ListLength(L);
bool k=ListDelete(L,3,e);
PrintList(L);
printf("%d\n",j);
printf("%d\n",e);
}