2.5线性表的链式表示和实现
最近在看《明朝那些事儿》颇有感慨,在文章开始之前容我啰嗦几句。昔明太祖朱元璋从一无所有之人到至高无上的皇帝,这期间的艰难困苦一言难尽……但是历史永远的记住了他,而在权力争夺路上倒下的普通人,历史只留下了四个字——灰飞烟灭。没有人会记住他们的名字,没有人会在乎他们的人生。唯有帝王将相才子佳人才有资格留下印记去证明他们来过这个星球。
倘若百年之后我年少时写的东西还能有幸被人搜索到的话,我想告诉那位可爱的读者:这些文章来自一个低洼山谷西侧小溪旁东墙墙角下杂草丛生中一朵不敢轻易凋谢的花蕾。
开讲喽!
单链表的基础操作,代码如下:
#include <iostream>
typedef int ElemType;
typedef int Status;
# define OK 1
# define ERROR 0
using namespace std;
//定义结点
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode,*LinkList;
//初始化
Status InitList(LinkList &L)
{
L=new LNode;
L->next=NULL;
return OK;
}
//销毁单链表
Status DestroyList(LinkList &L)
{
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
//显示单链表
void DisplayList(LinkList L)
{
LinkList p = L->next;
cout<<"单链表为:";
while(p)
{
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
//指定位置插入元素
Status ListInsert(LinkList L,int i,ElemType e)
{
LinkList p = L;
int j =0;
while(p&&j<i-1) //寻找第i-1个结点
{
p=p->next;
++j;
}
if (!p||j>i-1) // 左右越界
{
cout<<"输入序号不符合要求"<<endl;
return ERROR;
}
LinkList s = new LNode;
s->data = e;
s->next = p->next;//与后面结点连接
p->next = s;//与前面结点连接
cout<<"插入成功"<<endl;
return OK;
}
//尾插法创建单链表
void CreateList_R(LinkList &L,int n)
{
L = new LNode;
L->next = NULL;
LinkList r = L;
cout<<"请输入元素:";
for (int i=0; i<n; ++i)
{
LinkList p = new LNode;
cin>>p->data;
p->next=NULL;
r->next = p;
r = p;
}
cout<<"创建成功"<<endl;
}
//清空单链表
Status ClearList(LinkList L)
{
LinkList p,q;
p=L->next;//p指向第首元结点
while(p)//看是否到表尾
{
q=p->next;
delete p;
p = q;
}
L->next = NULL;//头结点指针域为空
cout<<"清空成功"<<endl;
return OK;
}
//判断单链表是否为空
bool ListEmpty(LinkList L)
{
if (L->next)
return true;
else
return false;
}
//求单链表的长度
int ListLength(LinkList L)
{
LinkList p;
p = L->next;//指向首元结点
int num = 0;
while(p)
{
++num;
p = p->next;
}
return num;
}
//获取单链表指定位置元素
void GetElem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
while(p&&j<i)
{
p = p->next;
++j;
}
if(!p||j>i)//位置越界
cout<<"输入的位置不合要求"<<endl;
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素是:"<<e<<endl;
}
}
//求前驱
Status pre_elem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
if (i==1)
{
cout<<"第一个元素没有前驱"<<endl;
return -1;
}
while(p&&j<i-1)
{
p = p->next;
++j;
}
if(!p||j>i-1)//位置越界
{
cout<<"输入的位置不合要求"<<endl;
return -1;
}
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素的前驱是:"<<e<<endl;
return -1;
}
}
//求后继
Status next_elem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
if (i==ListLength(L))
{
cout<<"最后一个元素没有后继"<<endl;
return -1;
}
while(p&&j<i+1)
{
p = p->next;
++j;
}
if(!p||j>i+1)//位置越界
{
cout<<"输入的位置不合要求"<<endl;
return -1;
}
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素的后继是:"<<e<<endl;
return -1;
}
}
//查找某元素的位置
void LocateElem(LinkList L,ElemType e)
{
LinkList p = L->next;
int j = 1;
while(p&&p->data!=e)
{
p=p->next;
++j;
}
if (j>ListLength(L))
cout<<"单链表中没有该元素"<<endl;
else
cout<<"元素"<<e<<"的位置是:"<<j<<endl;
}
//删除指定位置元素
Status ListDelete(LinkList L,int i,ElemType &e)
{
LinkList p = L;
int j = 0;
while(p->next&&j<i-1)
{
p = p->next;
++j;
}
if (!(p->next)||j>i-1)//看是否越界
{
cout<<"输入不符合要求"<<endl;
return ERROR;
}
LinkList r = p->next;//临时保存
p->next=r->next;//让第i-1个结点和第i+1个结点相连
e = r->data;
delete r;
cout<<"成功删除"<<"第"<<i<<"个元素"<<e<<endl;
return OK;
}
void show_help()
{
cout<<"******* Data Structure ******"<<endl;
cout<<"1----清空单链表"<<endl;
cout<<"2----判断单链表是否为空"<<endl;
cout<<"3----求单链表长度"<<endl;
cout<<"4----获取单链表指定位置元素"<<endl;
cout<<"5----求前驱"<<endl;
cout<<"6----求后继"<<endl;
cout<<"7----在单链表指定位置插入元素"<<endl;
cout<<"8----删除单链表指定位置元素"<<endl;
cout<<"9----显式单链表"<<endl;
cout<<"10----创建单链表"<<endl;
cout<<"11----求元素所在位置"<<endl;
cout<<" 退出,输入0"<<endl;
}
int main()
{
int operate_code;
show_help();
LinkList L;//定义线性表变量
InitList(L);//调用初始化线性表函数
while(1)
{
cout<<"";
cin>>operate_code;
if(operate_code==1)
{
ClearList(L);
}
else if (operate_code==2)
{
if (ListEmpty(L))
cout<<"单链表不为空"<<endl;
else
cout<<"单链表为空"<<endl;
}
else if (operate_code==3)
{
cout<<"单链表的长度为:"<<ListLength(L)<<endl;
}
else if (operate_code==4)
{
int n;
cout<<"输入要查找的元素位置:";
cin>>n;
GetElem(L,n);
}
else if (operate_code==5)
{
int n;
cout<<"请输入元素的位置:";
cin>>n;
pre_elem(L,n);
}
else if (operate_code==6)
{
int n;
cout<<"请输入元素的位置:";
cin>>n;
next_elem(L,n);
}
else if (operate_code==7)
{
int i;
ElemType e;
cout<< "要插入元素的位置:";
cin>>i;
cout<<"要插入元素的值:";
cin>>e;
ListInsert(L,i,e);
}
else if (operate_code==8)
{
int n,m;
cout<<"输入要删除元素的位置:";
cin>>n;
ListDelete(L,n,m);
}
else if (operate_code==9)
{
DisplayList(L);
}
else if (operate_code==10)
{
int n;
cout<<"请输入元素的个数:";
cin>>n;
CreateList_R(L,n);
}
else if (operate_code==11)
{
int e;
cout<<"输入元素的值:";
cin>>e;
LocateElem(L,e);
}
else if (operate_code==0)
{
break;
}
else
{
cout<<"\n操作码错误!!!"<<endl;
show_help();
}
}
DestroyList(L);//调用销毁线性表函数
return 0;
}
剖析:
1.定义结点
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode,*LinkList;
- 单链表是一个个的结点相连组成的。一个结点是由数据域和指针域这两部分组成的。
- LNode可以看成咱们定义的一个数据类型,之后定义结点就把这个当关键字用。
- LinkList为LNode类型的指针。
2.初始化
Status InitList(LinkList &L)
{
L=new LNode;
L->next=NULL;
return OK;
}
初始化是生成一个这样的东西:
- 在生成头结点时头指针也就有了,new把头结点的地址返回给头指针L。
- 之后我们把头结点的指针域(首元结点的地址)设置为NULL。
- 创建对L肯定有修改所以我们用引用。
3.销毁单链表
Status DestroyList(LinkList &L)
{
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
- 销毁肯定有修改所以用引用。
- 创建用new那么删除用delete,从左往右删。
3.显示单链表
void DisplayList(LinkList L)
{
LinkList p = L->next;
cout<<"单链表为:";
while(p)
{
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
- 显示无需修改所以直接传进去L就好了
- 为了防止对单链表的破坏我们新定义了一个LNode类型的指针p,并且在开始时把p指向首元结点的地址。
4.在指定位置插入元素
Status ListInsert(LinkList L,int i,ElemType e)
{
LinkList p = L;
int j =0;
while(p&&j<i-1) //寻找第i-1个结点
{
p=p->next;
++j;
}
if (!p||j>i-1) // 左右越界
{
cout<<"输入序号不符合要求"<<endl;
return ERROR;
}
LinkList s = new LNode;
s->data = e;
s->next = p->next;//与后面结点连接
p->next = s;//与前面结点连接
cout<<"插入成功"<<endl;
return OK;
}
- 因为要找第i-1个结点所以我们让p先指向头结点。
- 当要插入的位置大于(表长+1)时,第 个元素就会一口气查询到最后一个元素。这时p就是最后一个结点的指针域即为NULL。此时if中!p成立。
- 当要插入的位置是0时,while循环不执行。j > i -1成立。
- 要先将要插入的结点和后面的结点相连,如果和前面先相连的话就把后面结点的位置丢了。
5.删除指定位置元素
Status ListDelete(LinkList L,int i,ElemType &e)
{
LinkList p = L;
int j = 0;
while(p->next&&j<i-1)
{
p = p->next;
++j;
}
if (!(p->next)||j>i-1)//看是否越界
{
cout<<"输入不符合要求"<<endl;
return ERROR;
}
LinkList r = p->next;//临时保存
p->next=r->next;//让第i-1个结点和第i+1个结点相连
e = r->data;
delete r;
cout<<"成功删除"<<"第"<<i<<"个元素"<<e<<endl;
return OK;
}
- 如果表长为n,那么插入可以插入第n+1个结点。但是删除只能删除到第n个结点。这就导致要寻找的第i-1个结点最多到倒数第二个结点,但是只有最后一个结点的指针域才是NULL,所以我们把上面的p全换成了p->next.
- !(p->next)是右越界,j>i-1是左越界。
- 因为一个结点指针域的地址和下一个结点的地址一样,只用p表示不过来,所以我们有整了一个 r 用来临时保存。
- 在第i-1个结点和第i+1个结点连接起来后就可以delete了。
6.尾插法创建单链表
//尾插法创建单链表
void CreateList_R(LinkList &L,int n)
{
L = new LNode;
L->next = NULL;
LinkList r = L;
cout<<"请输入元素:";
for (int i=0; i<n; ++i)
{
LinkList p = new LNode;
cin>>p->data;
p->next=NULL;
r->next = p;
r = p;
}
cout<<"创建成功"<<endl;
}
- 前插法还得倒叙输入,我们采用尾插法。
- 先创建一个头结点,然后不断创建尾结点并与前面的相连。
7.清空单链表
Status ClearList(LinkList L)
{
LinkList p,q;
p=L->next;//p指向第首元结点
while(p)//看是否到表尾
{
q=p->next;
delete p;
p = q;
}
L->next = NULL;//头结点指针域为空
cout<<"清空成功"<<endl;
return OK;
}
- 和销毁不同的是清空是从首元结点开始delete的,最后再把头结点的指针域改成空。
8.判断单链表是否为空
bool ListEmpty(LinkList L)
{
if (L->next)
return true;
else
return false;
}
- 只要有一个就不算空,所以只要判断首元结点是否为空就好了。
9.求单链表的长度
int ListLength(LinkList L)
{
LinkList p;
p = L->next;//指向首元结点
int num = 0;
while(p)
{
++num;
p = p->next;
}
return num;
}
- 这个没啥,从首元结点开始计数。
10.获取指定位置的元素
void GetElem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
while(p&&j<i)
{
p = p->next;
++j;
}
if(!p||j>i)//位置越界
cout<<"输入的位置不合要求"<<endl;
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素是:"<<e<<endl;
}
}
- 这里把函数类型改成int然后加上返回值也是一样的。我这里用的是void型函数所以就不写返回值了。
- 这个查看就不用像删除和插入一样要改变结点指针域所以直接去找第i个结点就好了,不用找第i-1个结点。
- 和之前一样,当右边越界时!p成立,左侧越界时j>i成立。
11.求前驱
Status pre_elem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
if (i==1)
{
cout<<"第一个元素没有前驱"<<endl;
return -1;
}
while(p&&j<i-1)
{
p = p->next;
++j;
}
if(!p||j>i-1)//位置越界
{
cout<<"输入的位置不合要求"<<endl;
return -1;
}
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素的前驱是:"<<e<<endl;
return -1;
}
}
- 这里因为终止函数的条件比较多所以我们用return比较方便,故把void型改成Status型(即int型)。
- 在最开始加一个判断是不是第一个元素,然后把上一个函数所用的i改成i-1就好了。
12.求后继
Status next_elem(LinkList L,int i)
{
ElemType e;
LinkList p = L->next;
int j = 1;
if (i==ListLength(L))
{
cout<<"最后一个元素没有后继"<<endl;
return -1;
}
while(p&&j<i+1)
{
p = p->next;
++j;
}
if(!p||j>i+1)//位置越界
{
cout<<"输入的位置不合要求"<<endl;
return -1;
}
else
{
e = p->data;
cout<<"单链表中第"<<i<<"个元素的后继是:"<<e<<endl;
return -1;
}
}
- 和求前驱类似。就是最开始判断一下是不是最后一个元素然后把查询指定位置函数的i都改成i+1就ok了。
13.查询某元素位置
void LocateElem(LinkList L,ElemType e)
{
LinkList p = L->next;
int j = 1;
while(p&&p->data!=e)
{
p=p->next;
++j;
}
if (j>ListLength(L))
cout<<"单链表中没有该元素"<<endl;
else
cout<<"元素"<<e<<"的位置是:"<<j<<endl;
}
- 从首结点的数据域开始比较。要是从头到尾都没找到那 j 就会比表长大1.
单链表的优点:
缺点:
已知单链表LA和LB序列按值非递减排列,归并LA与LB得新链表LC,LC的元素也按值非递减排列。
注意:非递减和递增不同。1 1 1 这个序列也是非递减的。
#include <iostream>
#define OK 1
using namespace std;
typedef int ElemType;
typedef int Status;
//定义结点
typedef struct LNode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode,*LinkList;
//初始化
Status InitList(LinkList &L)
{
L=new LNode;
L->next=NULL;
return OK;
}
//尾插法创建单链表
void CreateList_R(LinkList &L,int n)
{
L = new LNode;
L->next = NULL;
LinkList r = L;
cout<<"请输入元素:";
for (int i=0; i<n; ++i)
{
LinkList p = new LNode;
cin>>p->data;
p->next=NULL;
r->next = p;
r = p;
}
cout<<endl;
}
//显示单链表
void DisplayList(LinkList L)
{
LinkList p = L->next;
while(p)
{
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
//按非递减排列进行归并
void MergeList(LinkList &LA,LinkList &LB,LinkList &LC)
{
LinkList pa = LA->next;
LinkList pb = LB->next;
LC = LA;//用LA的头结点
LinkList pc = LC;
while (pa&&pb)
{
if (pa->data <= pb->data)
{
pc->next = pa;//小的放进LC里
pc = pa;//前移方便下一次next
pa = pa->next;//用下一个元素进行对比
}
else
{ //同上
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa?pa:pb;//剩余的按原来顺序全放入LC中
delete LB;//释放LB的头结点
}
int main()
{
LinkList LA,LB,LC;//定义线性表变量
InitList(LA);
InitList(LB);
InitList(LC);
int n;
cout<<"请输入LA的元素个数:";
cin>>n;
CreateList_R(LA,n);
cout<<"请输入LB的元素个数:";
cin>>n;
CreateList_R(LB,n);
MergeList(LA,LB,LC);
cout<<"按非递减排列进行归并为:";
DisplayList(LC);
return 0;
}
剖析:
1.归并函数
void MergeList(LinkList &LA,LinkList &LB,LinkList &LC)
{
LinkList pa = LA->next;
LinkList pb = LB->next;
LC = LA;//用LA的头结点
LinkList pc = LC;
while (pa&&pb)
{
if (pa->data <= pb->data)
{
pc->next = pa;//小的放进LC里
pc = pa;//前移方便下一次next
pa = pa->next;//用下一个元素进行对比
}
else
{ //同上
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa?pa:pb;//剩余的按原来顺序全放入LC中
delete LB;//释放LB的头结点
}
- 这个也挺好理解,因为这两个序列都是非递减的所以就从第一个开始比较,不断把小的给LC就好了。
- pc->next = pa?pa:pb;如果pa不为空就把pa剩下的序列给LC,反正剩下的也是非递减的。