文章目录
ADT抽象数据类型
ADT 单链表(LinkList)
Data
同线性表。元素具有相同的类型,相邻的元素具有前驱和后继关系。
Operation
InitlList(LinkList &L);//初始化单链表
List_HeadInsert(LinkList &L);//头插法建立单链表
List_TrailInsert(LinkList &L);//尾插法建立单链表
PrintList(LinkList &L);//打印单链表
GetElemByOrder(LinkList &L,int i);//按位序查找元素
GetElemByValue(LinkList &L,ElemType e);//按值查找
GetLenth(LinkList &L);//返回单链表的长度
InsertByPriorNode(Lnode *p,ElemType e);//单链表的前插
InsertByPriorNode(Lnode *p,ElemType e);//单链表的后插
ListInsert(LinkList &L,int i,ElemType e);//带头结点按位序插入
NListInsert(LinkList &L,int i,ElemType e);//不带头结点按位序插入
DeletNode(Lnode *p);//删除指定结点的数据.
ListDelete(LinkList &L,int i,ElemType &e);//删除单链表
endADT
单链表的结构体定义
typedef int ElemType;
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}Lnode,*LinkList;
单链表的初始化
/* 初始化链表,带头结点*/
bool InitlList(LinkList &L){
L=(LinkList)malloc(sizeof(Lnode));
if(L==NULL)return false;//空间分配失败
L->next=NULL;
return true;
}
单链表的创建——头插法
/*头插法*/
LinkList List_HeadInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(Lnode));
L->next=NULL;//初始化尾空链表
Lnode *s;
cin>>x;
while(x!=9999){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
s->next=L->next;
L->next=s;
cin>>x;
}
return L;
}
单链表的创建——尾插法
/* 尾插法 */
LinkList List_TrailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(Lnode));
Lnode *r=L,*s;
cin>>x;
while(x!=9999){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
r->next=s;
r=s;
cin>>x;
}
r->next=NULL;
return L;
}
单链表的尾插法和头插法注意的事项:
采用尾插法,需要最后指明L的尾指针的指向为空,并且需要两个指针
采用头插法,需要一开始就要指定L的尾指针为空,不然遍历的时候,会死循环。
打印链表
void PrintList(LinkList &L){
Lnode *p=L->next;//这里默认跳过了头节点,也可以改成L
while(p){//while(p)表示当前的指向,这里会判断尾指针指向的NULL,为了安全,可以改成p->next!=NULL
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
查找值——按位查找
/*
按位查找
1.判断位序是否合法
2.定义结点指向,开始遍历,直到i的位置
3.返回节点
*/
Lnode * GetElemByOrder(LinkList &L,int i){
if(i<1){
cout<<"查找位置不合法,查找失败!";
return false;
}
Lnode *p=L->next;
int j=1;
while(p!=NULL&j<i){
p=p->next;
j++;
}
return p;
}
查找值——按值查找
/* 按值查找*/
Lnode * GetElemByValue(LinkList &L,ElemType e){
Lnode *p=L->next;
while(!p){
if(p->data==e)
return p;
p=p->next;
}
}
返回单链表的长度
/*求单链表的长度*/
int GetLenth(LinkList &L){
int len=1;
Lnode *p=L->next;//从第一个节点开始统计
while(p->next!=NULL) {
p=p->next;
len++;
}
return len;
}
前插
/*
如果采用扫描定位到当前待插入的结点之前,那么时间复杂度就是O(N)
这里可以采用后插的方法来实现,把插入的结点中的数据修改为当前结点的数据,
再把当前结点的数据更改为待插入的元素
O(1)
*/
bool InsertByPriorNode(Lnode *p,ElemType 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;
}
后插
/* 后插操作
1.判断传入的结点是否为空
不为空则执行2
2.申请结点s,把e装进所申请结点中的data中
3.修改申请结点s的指向,把s放在p的后面,即s->next=p->next
4.再把p的指向更改为s
O(1)
*/
bool InsertByPriorNode(Lnode *p,ElemType 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;
}
带头节点的按位序插入
/*
带头结点
按位序插入:
1.判断插入位置是否合法
2.定位到需要插入的i-1位置
3.开始插入数据
*/
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1) {
cout<<"插入的位置有误!"<<endl;
return false;
}
Lnode *p=L;//从头节点开始
int j=0;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
/*
从这里开始的代码可以进行封装,调用 InsertByNextNode()函数就可以实现后插操作。
*/
if(p==NULL){
cout<<"插入错误!"<<endl;
return false;
}
Lnode *s=(Lnode * )malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
不带头节点的按位序插入
/*
与带头结点的相比,仅仅多了一道处理头节点的手续。
所以带头结点的单链表利于处理。
*/
bool NListInsert(LinkList &L,int i,ElemType e){
if(i<1) {
cout<<"插入的位置有误!"<<endl;
return false;
}
//插入第一个结点的操作与其他结点的操作不同
if(i==1) {
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
s->data=e;
s->next=L;
L=s;
return true;
}
Lnode *p=L;//从头节点开始
int j=0;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL){
cout<<"插入错误!"<<endl;
return false;
}
Lnode *s=(Lnode * )malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
删除指定节点的数据
/*
删除指定结点的数据
1.找到当前结点的下一个结点
2.将下一个结点的数据替换掉当前结点
3.释放下一个结点
O(1)
*/
bool DeletNode(Lnode *p){
if(p==NULL)return false;
//如果p为最后一个结点,那么q的指向为空,空中的数据则会引发空指针异常。
Lnode *q=p->next;
p->data=q->data;
p->next=q->next;
free(q) ;
return true;
}
删除单链表
/*
按位序删除,带头结点
O(N)
*/
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1) {
cout<<"删除结点有误!" <<endl;
return false;
}
Lnode *p=L;
int j=0;//从头结点开始定位
while(p!=NULL&j<i-1) {
p=p->next;
j++;
}
if(p==NULL){
//链表大小为5,删除为7,后面的结点不存在
cout<<"删除失败!"<<endl;
return false;
}
if(p->next==NULL){
//当前所要删除的第i个结点不存在 ,定位在i-1之后没有结点,删除5,4之后就没有结点了,
cout<<"删除失败!" ;
return false;
}
/*
e=p->next->data;
free(p->next);
直接这么写的后果就是造成了链表最后指向了脏数据
所以,要引入指针变量
*/
Lnode *q=p->next;
e=q->data;//带回所要删除的对象数据
p->next=q->next;//这一步很关键,不指明p后面的元素,直接删除会造成断链或者指向脏数据
free(q) ;
return true;
}
循环单链表的初始化
/*初始化循环单链表*/
bool InitiList(LinkList &L){
L=(Lnode * )malloc(sizeof(Lnode));
if(L=NULL)//初始化失败,
return false;
L->next=L;
return true;
}
循单判空
/*判断循环单链表是否为空*/
bool IsEmpty(LinkList &L){
return (L->next==L)?true:false;
}
判断节点是否为尾结点
/*判断节点p是否为循环单链表的表尾节点*/
bool isTrail(LinkList &L, Lnode *p){
return(p->next==L)?true:false;
}
由于单链表跟循环单链表之间的区别,仅仅在于尾指针的指向是否是L,其它的代码实现方式跟单链表相差不多,仅在于尾指针的处理,所以,下面只给出了单链表的具体实现。
代码实现—单链表
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int ElemType;
/*定义链表结构体的类型 */
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}Lnode,*LinkList;
/* 初始化链表,带头结点*/
bool InitlList(LinkList &L){
L=(LinkList)malloc(sizeof(Lnode));
if(L==NULL)return false;//空间分配失败
L->next=NULL;
return true;
}
/*判断一个链表是否为空*/
bool isEmpty(LinkList &L){
return (L->next==NULL)?true:false;
}
/* 尾插法 */
LinkList List_TrailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(Lnode));
Lnode *r=L,*s;
cin>>x;
while(x!=9999){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
r->next=s;
r=s;
cin>>x;
}
r->next=NULL;
return L;
}
/*头插法*/
LinkList List_HeadInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(Lnode));
L->next=NULL;//初始化尾空链表
Lnode *s;
cin>>x;
while(x!=9999){
s=(Lnode *)malloc(sizeof(Lnode));
s->data=x;
s->next=L->next;
L->next=s;
cin>>x;
}
return L;
}
/*输出这个链表*/
void PrintList(LinkList &L){
Lnode *p=L->next;
while(p){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
/*按位查找*/
Lnode * GetElemByOrder(LinkList &L,int i){
if(i<1){
cout<<"查找位置不合法,查找失败!";
return false;
}
Lnode *p=L->next;
int j=1;
while(p!=NULL&j<i){
p=p->next;
j++;
}
return p;
}
/* 按值查找*/
Lnode * GetElemByValue(LinkList &L,ElemType e){
Lnode *p=L->next;
while(!p){
if(p->data==e)
return p;
p=p->next;
}
}
/*求单链表的长度*/
int GetLenth(LinkList &L){
int len=1;
Lnode *p=L->next;//从第一个节点开始统计
while(p->next!=NULL) {
p=p->next;
len++;
}
return len;
}
/*
带头结点
按位序插入:
1.判断插入位置是否合法
2.定位到需要插入的i-1位置
3.开始插入数据
*/
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1) {
cout<<"插入的位置有误!"<<endl;
return false;
}
Lnode *p=L;//从头节点开始
int j=0;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
/*
从这里开始的代码可以进行封装,调用 InsertByNextNode()函数就可以实现后插操作。
*/
if(p==NULL){
cout<<"插入错误!"<<endl;
return false;
}
Lnode *s=(Lnode * )malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
/*
不带头结点
*/
bool NListInsert(LinkList &L,int i,ElemType e){
if(i<1) {
cout<<"插入的位置有误!"<<endl;
return false;
}
//插入第一个结点的操作与其他结点的操作不同
if(i==1) {
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
s->data=e;
s->next=L;
L=s;
return true;
}
Lnode *p=L;//从头节点开始
int j=0;
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL){
cout<<"插入错误!"<<endl;
return false;
}
Lnode *s=(Lnode * )malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
/* 后插操作
1.判断传入的结点是否为空
不为空则执行2
2.申请结点s,把e装进所申请结点中的data中
3.修改申请结点s的指向,把s放在p的后面,即s->next=p->next
4.再把p的指向更改为s
O(1)
*/
bool InsertByNextNode(Lnode *p,ElemType e){
if(p==NULL)
return false;
Lnode *s =(Lnode *)malloc(sizeof(Lnode));
//这里可以进行一个判断,如果s申请空间失败,也是插入失败
if(s==NULL) return false;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
/*
如果采用扫描定位到当前待插入的结点之前,那么时间复杂度就是O(N)
这里可以采用后插的方法来实现,把插入的结点中的数据修改为当前结点的数据,
再把当前结点的数据更改为待插入的元素
O(1)
*/
bool InsertByPriorNode(Lnode *p,ElemType 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;
}
/*
按位序删除,带头结点
O(N)
*/
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1) {
cout<<"删除结点有误!" <<endl;
return false;
}
Lnode *p=L;
int j=0;//从头结点开始定位
while(p!=NULL&j<i-1) {
p=p->next;
j++;
}
if(p==NULL){
//链表大小为5,删除为7,后面的结点不存在
cout<<"插入失败!"<<endl;
return false;
}
if(p->next==NULL){
//当前所要删除的第i个结点不存在 ,定位在i-1之后没有结点,删除5,4之后就没有结点了,
cout<<"删除失败!" ;
return false;
}
/*
e=p->next->data;
free(p->next);
直接这么写的后果就是造成了链表最后指向了脏数据
所以,要引入指针变量
*/
Lnode *q=p->next;
e=q->data;//带回所要删除的对象数据
p->next=q->next;//这一步很关键,不指明p后面的元素,直接删除会造成断链或者指向脏数据
free(q) ;
return true;
}
/*
删除指定结点的数据
1.找到当前结点的下一个结点
2.将下一个结点的数据替换掉当前结点
3.释放下一个结点
O(1)
*/
bool DeletNode(Lnode *p){
if(p==NULL)return false;
//如果p为最后一个结点,那么q的指向为空,空中的数据则会引发空指针异常。
Lnode *q=p->next;
p->data=q->data;
p->next=q->next;
free(q) ;
return true;
}
int main(){
LinkList L;
InitlList(L);
List_TrailInsert(L);
ListInsert(L,3,5) ;
PrintList(L);
int e;
ListDelete(L,3,e);
cout<<"被删除的元素是:"<<e<<endl;
PrintList(L);
return 0;
}