本节大纲内容
-
线性表的概念,线性表的抽象数据类型,基本操作
线性表
线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列。除第一个元素外,每个元素有且仅有一个直接前驱。除最后一个元素外,每个元素有且仅有一个直接后继。
这种线性有序的逻辑结构正是线性表名字的由来。线性表的特点
(1)表中元素的个数有限。
(2)表中元素具有逻辑上的顺序性,在序列中各个元素排序有先后次序。
(3)表中元素都是数据元素,每一个元素都是单个元素。
(4)表中元素的数据类型都相同。这意味着每一个元素占有相同大小的存储空间。
(5)表中元素具有抽象性。即仅讨论元素间的逻辑关系,不考虑元素究竟表示什么内容。注意
(1)线性表是一种逻辑结构,表示元素之间一对一的相邻关系。
(2)顺序表和链表是指存储结构,两者属于不同层面的概念,因此不要将其混淆。线性表的基本操作
一个数据结构的基本操作是指 其最核心、最基本的操作。其他较复杂的操作可以通过调用其基本操作来实现。
(1)InitList(&L):初始化表。构造一个空的线性表。
(2)Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
(3)Locate(L,e):按值查找操作。在表L中查找具有给定关键值的元素。
(4)GetElem(L,i):按位查找操作,获取表L中第i个位置的元素值。
(5)ListInsert(&L,i,e):插入操作。在表L中第i个位置上插入指定元素e。
(6)ListDelete(&L,i&e):删除操作。删除表中第i个位置的元素,并用e返回被删除元素的值。
(7)PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
(8)Empty(L):判空操作。若L为空表,返回true,否则返回false。
(9)DestroyList(&L);销毁操作。销毁线性表,并释放线性表L所占用的内存空间。注意
基本操作的实现取决于采用的存储结构,存储结构不同,算法的实现也不同。 -
线性表的顺序存储结构:静态分配,动态分配
顺序表
线性表的顺序存储又称顺序表。它是用一种地址连续的存储单元,依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上用相邻。
注意
线性表中的元素的位序是从1开始的,而数组中元素的下标是从0开始的。假定线性表的元素类型是ElemType,线性表的顺序存储类型描述为:
#define MaxSize 50 //定义线性表的最大长度 typedef struct{ ElemType data[MaxSize]; //顺序表的元素 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义
静态分配
大小和空间事先已经固定,一旦空间占满,再加入新的数据将产生溢出,就会导致程序崩溃。动态分配
存储空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满可以另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储空间的目的,而不需要一次性的划分所有所需空间给线性表。动态分配过程
(1)申请一个更大的空间
(2)把原来空间的东西复制到新的空间内
(3)如果新的空间不为空则分配成功,否则分配失败#define InitSize 100 //表长度的初始定义 typedef struct{ ElenType *data; //指示动态分配数组的指针 int MaxSize,length; //数组的最大容量和当前个数 }SqList; //动态分配数组顺序表的类型定义
动态分配的函数realloc
realloc函数的用法C语言的初始动态分配语句为
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
C++的初始动态分配语句为
L.data = new ElemType[InitSize];
注意
动态分配并不是链式存储,同样还是属于顺序存储结构,其物理结构没有变化,依然是随机存取方式,只是分配的空间大小可以在运行时决定。
(1)顺序表最主要的特点是随机访问。
(2)顺序表的存储密度高,每个节点只存储数据元素。
(3)顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量的元素。 -
顺序表的插入删除算法,移动元素次数分析
1、插入操作
在顺序表的第 i (1 ≦ i ≦ L.length+1) 个位置上插入新元素e。
算法思想:如果输入的i不合法,则返回false,表示插入失败;否则,将顺序表的第 i 个元素以及以后的所以元素右移一个位置,腾出一个空位插入新元素e,顺序表长度增加1,插入成功,返回true。
注意
在算法核心为进行之前应检验待运算数据的合法性。
bool ListInsert(SqList &L,int i,ElemType e){ //本算法实现将元素e插入到顺序表L中的第i个位置。 if(i < 1 || i > L.length + 1) //检验i的合法性。L.length + 1是因为线性表位从1开始。 return false; if(L.length > = MaxSize) //检验线性表的合法性。当前存储空间已满,不能插入 return false; for(int j = L.length;j > i;j--) //将第i个元素及之后的元素后移。注意从尾部开始移动。 //j = L.length是因为数组下标从0开始 L.data[j] = L.data[j-1]; //这里以数组为例,注意数组下标和线性表位的区别 L.data[i-1] = e; //在位置i处放入e L.length++; //线性表长度加1 return true; }
算法分析
最好情况:在表尾插入,元素后移语句将不执行,时间复杂度为O(1)。
最坏情况:在表头插入,元素后移语句将执行n次,时间复杂度为O(n)。
平均情况:假设Pi是在第i个位置上插入一个节点的概率,则在长度为n的线性表中插入一个节点时所需移动节点的平均次数为2、删除操作
算法思想:
删除顺序表L中第i(1 ≦ i ≦ L.length)个位置的元素,成功则返回true,并将被删除的元素用引用变量e返回,否则返回false。bool ListDelete(SqList &L,int i,ElemType &e){ //本算法实现删除顺序表L中第i个位置的元素 if(i < 1 || i > L.length) //检验i的合法性。 return false; e = L.data[i-1]; //将被删除的元素赋给e for(int j = i;j < L.length;i++) L.data[j - 1] = L.data[j]; //将第i个位置之后的元素前移 L.length--; //线性表长度减1 return true; }
算法分析:
最好情况:删除表尾元素,无须移动元素,时间复杂度为O(1)。
最坏情况:删除表头元素,需要移动除第一个元素外的所以元素,时间复杂度为O(n)。
平均情况:假设Pi是删除第i个位置上节点的概率,则在长度为n的线性表中删除一个节点所需移动节点的平均次数为因此,线性表删除算法的平均时间复杂度为O(n)。
3、按值查找(顺序查找)
算法思想:在顺序表中查找第一个等于e的元素,并返回其位序。
int LocateElem(SqList L,ElemType e){ //本算法实现查找顺序表中值为e的元素,如果查找成功,返回元素位序,否则返回0。 int i; for(i = 0;i < L.length;i++) if(L.data[i] == e) return i + 1; //下标为i的元素等于e,则其位序为i+1。 return 0; //退出循环,查找失败。 }
算法分析:
最好情况:查找元素就在表头,仅需比较一次,时间复杂度为O(1)。
最坏情况:查找元素在表尾(或不存在)时,需要比较n次,时间复杂度为O(n)。
平均情况:假设Pi是查找的元素在第i位置上的概率,则在长度为n的线性表中查找为e的元素所需比较的平均次数为
![](https://i-blog.csdnimg.cn/blog_migrate/fcf4c944546b0cb55d69dd0f4c86d2a9.png)
-
顺序存储结构的优缺点,引出单链表的结构类型定义
1、优点
(1)是一种随机存取的结构,存取任何元素的时间都是一个常数,速度快。
(2)结构简单,逻辑上相邻的元素物理上也相邻
(3)不使用指针,节省存储空间
2、缺点
(1)插入和删除元素要移动大量元素,消耗大量时间;
(2)需要一个连续的存储空间;
(3)插入元素可能发生“溢出”;
(4)自由区中的存储空间不能被其它数据占用(共享)。由于顺序表的插入、删除操作需要移动大量的元素,影响了运行效率,由此引入了线性表的链式存储。
链式存储线性表时,不需要使用地址连续的存储单元,它是通过链建立起数据元素之间的逻辑关系,因此,对线性表的插入、删除不需要移动元素,而只需要修改指针。线性表的链式存储又称单链表,它是通过一组任意的存储单元来存储线性表中的数据元素。
为了建立起数据元素之间的线性关系,对每个链表节点,除了存放元素自身的信息外,还需要存放一个指向其后继的指针。单链表节点类型描述如下:
typedef struct LNode{ //定义单链表节点类型 ElemType data; //数据域 struct LNode *next; //指针域 }LNode,*LinkList;
利用单链表可以解决顺序表需要大量的连续存储空间的缺点,但是单链表附加指针域,也存在浪费存储空间的缺点。由于单链表的元素是离散地分布在存储空间中的,所以单链表是非随机存取的存储结构。查找某个节点时需要从表头开始遍历,依次查找。
通常"头指针"来标识一个单链表,如单链表L,头指针为“NULL”时则表示一个空表。此外,为了操作上的方便,在单链表第一个节点之前附加一个节点,成为 头结点 。头结点的数据域可以不设任何信息,也可以记录表长等相关信息。头结点的指针域指向线性表的第一个元素节点。
头结点和头指针的区分:
不管带不带头结点,头指针始终指向链表的第一个节点。而头结点是带头结点链表中的第一个节点,节点内通常不存储信息。
引入头结点后,可以带来两个优点:
(1)由于开始节点的位置被存储在头结点的指针域中,所以在链表的第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理。
(2)无论链表是否为空,其头指针是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理也就统一了。 -
单链表的算法:生成先进先出单链表,后进先出单链表
先进先出单链表即尾插法建立单链表。
后进先出单链表即头插法建立单链表。
这两种算法属于单链表基本操作,见末尾补充环节。 -
单链表的算法:生成不带表头的递增有序单链表,生成带表头的递增有序单链表
算法1:生成不带表头的递增有序单链表
算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
LinkList insert_up(LinkList L,LNode *s);
int lengthList(LinkList L);
int main(int argc, const char * argv[]) {
LinkList L;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L)<<'\n';
cout<<'\n';
return 0;
}
LinkList insert_up(LinkList L,LNode *s){
LNode *p = L,*r = L->next;
if(r == NULL&&p->data == 999){
p->data = s->data;
return L;
}
while(p){
if(s->data >= p->data){
if(r == NULL || s->data < r->data)
{
s->next = r;
p->next = s;
return L;
}
}
p = p->next;
r = r->next;
}
return L;
}
LinkList Create_EndList(){
LinkList L = new LNode;
L->data = 999;
L->next = NULL;
LNode *s;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
s->next = NULL;
L = insert_up(L,s);
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
return L;
}
bool display_List(LinkList L){
LNode *cheak = L;
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
int lengthList(LinkList L){
int i = 0;
LNode *p = L;
while(p){
i++;
p = p->next;
}
return i;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/a36a3185eb16b9fdd878e472be501557.png)
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
LinkList insert_up(LinkList L,LNode *s);
int lengthList(LinkList L);
int main(int argc, const char * argv[]) {
LinkList L;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L)<<'\n';
cout<<'\n';
return 0;
}
LinkList insert_up(LinkList L,LNode *s){
int i=1; //设置i是为了识别新来的节点小于链表的第一个节点值时
LNode *p = L->next;
LNode *r = NULL;
if(p == NULL){
L->next = s;
return L;
}
while(p){
r = p->next;
if(s->data >= p->data){
if(r == NULL || s->data < r->data)
{
s->next = r;
p->next = s;
return L;
}
}
else{ //如果新来的节点小于第一个节点值,就用头插法,把它查到第一个位置
if(i==1){
s->next = p;
L->next = s;
return L;
}
}
p = p->next;
i++;
}
return L;
}
LinkList Create_EndList(){
LinkList L = new LNode;
L->next = NULL;
LNode *s;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
s->next = NULL;
L = insert_up(L,s);
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
int lengthList(LinkList L){
int i = 0;
LNode *p = L->next;
while(p){
i++;
p = p->next;
}
return i;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/b19be4b96745dcfee32279b7cdad6e36.png)
算法思想:选取其中一个链表1不变,从第一个节点依次取出链表2的节点,把每个节点按递增次序插入到链表1中,得到新的合并链表1。
注意:此算法还可以改进,因为两个链表事先都是递增的,那么每次插入一个节点,同时记录插入的位置,那么下次再插入节点时,只需要从上一个插入位置之后开始插入就可以了。
算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_up_EndList();
bool display_List(LinkList L);
LinkList insert_up(LinkList L,LNode *s);
int lengthList(LinkList L);
LinkList sort_up(LinkList L1,LinkList L2);
int main(int argc, const char * argv[]) {
LinkList L1=NULL,L2=NULL,L;
cout<<"创建第1个递增链表:"<<'\n';
L1 = Create_up_EndList();
if(display_List(L1))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L1)<<'\n';
cout<<"创建第2个递增链表:"<<'\n';
L2 = Create_up_EndList();
if(display_List(L2))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L2)<<'\n';
L = sort_up(L1,L2);
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L)<<'\n';
cout<<'\n';
return 0;
}
LinkList insert_up(LinkList L,LNode *s){
int i=1;
LNode *p = L->next;
LNode *r = NULL;
if(p == NULL){
L->next = s;
return L;
}
while(p){
r = p->next;
if(s->data >= p->data){
if(r == NULL || s->data < r->data)
{
s->next = r;
p->next = s;
return L;
}
}
else{
if(i==1){
s->next = p;
L->next = s;
return L;
}
}
p = p->next;
i++;
}
return L;
}
LinkList Create_up_EndList(){
LinkList L = new LNode;
L->next = NULL;
LNode *s;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
s->next = NULL;
L = insert_up(L,s);
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
LinkList sort_up(LinkList L1,LinkList L2){
LNode *p = L2->next;
LNode *s;
while(p){
s = new LNode;
s->data = p->data;
s->next = NULL;
L1 = insert_up(L1,s);
p = p->next;
}
return L1;
}
int lengthList(LinkList L){
int i = 0;
LNode *p = L->next;
while(p){
i++;
p = p->next;
}
return i;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/45fb5a2563548c6068e7429827a11dd4.png)
第二种算法
输入:两单链表的头指针
输出:合并后单链表的头指针
在这里只写出函数,可以画出示意图仔细体会此算法的思想。
LinkList sort_up(LinkList L1,LinkList L2){
LNode *pa,*pb,*pc;
pa=La->next; //pa指向表La的首结点
pb=Lb->next; //pb指向表Lb的首结点
pc=La; //使用表La的头结点,pc为尾指针
free(Lb); //释放表Lb的头结点
while(pa&&pb){ //表a表b中均有节点
if (pa->data<=pb->data) { //取表La的一个结点
pc->next=pa; //插在表Lc的尾结点之后
pc=pa; //变为表Lc新的尾结点
pa=pa->next; //移向表La下一个结点
}
else { //取表Lb的一个结点
pc->next=pb; //插在表Lc的尾结点之后
pc=pb; //变为表Lc新的尾结点
pb=pb->next; //移向表Lb下一个结点
}
}
if (pa) pc->next=pa; //插入表La的剩余段
else pc->next=pb; //插入表Lb的剩余段
return La;
}
循环单链表:
循环单链表和单链表的区别在于,表中最后一个节点的指针不是NULL,而改为指向头节点,从而整个链表形成一个环。
因此,循环单链表的判空条件不是头节点的指针是否为空,而是它的指针是否等于头指针。
循环单链表的插入、删除算法与单链表几乎一样,不同的是如果操作在表尾进行,则执行的操作不同,但也仅仅修改了指针而已,以至于保持循环的特性。
在单链表中只能从表头节点开始往后遍历整个表,而循环链表可以从表的任何节点开始遍历整个表。
注意:
有时对单链表常做的操作在表头和表尾进行的,此时可对循环链表不设头指针而仅设尾指针,从而使得操作效率更高。其原因是若设的是头指针,对表尾进行操作需要O(n)的时间复杂度,而如果设的是表尾指针,对于表头与表尾的操作都只需要O(1)的时间复杂度。
循环双链表:
由循环单链表的定义不难推出循环双链表,不同的是在循环双链表中,头节点的prior指针还要指向表尾节点。
在循环双链表L中,某节点*p为表尾节点时,p->next==L;当循环双链表为空表时,其头节点的prior指针和next指针都指向L。
在此只对循环双链表的插入、删除写出算法。
(1)插入节点
双向链表只是比单链表多了一个前驱指针,循环双向链表只是把双链表尾部的后继指针由NULL改为头而已,算法大致没有差别,只有关键代码有区别而已。这里只写出关键代码。
双链表中节点类型描述如下:
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinklist;
插入操作:
在双链表中p所指的节点之后插入节点*s,其指针变化过程如下代码:
(1)s->next = p->next;
(2)p->next->prior = s;
(3)s->pripr = p;
(4)p->next = s;
插入节点应注意:
p的后继指针不能丢,要用s连接p的后继。
(2)删除节点
删除双链表中节点 *p的后继节点 *q,其指针变化过程如下代码:
p->next = q->next;
q->next->prior = p;
free(q);
-
多项式的链表表示,算法思想
这里只要求算法思想,那我就只写出算法思想,如有需要可自行按思想写出算法。
以一元多项式为例,未知变量为x。
分析1:
多项式由什么组成?答:由多项式的每个项组成。
分析2:
每个项由什么组成?答:符号、系数、次幂
分析3:
如何存放这些项?答:建立结构体。typedef struct LNode{ char sign; //存放符号:+,- int data; //存放系数,这里以整数为例 int time; //存放次幂 struct DNode *next; }LNode,*Linklist;
通过以上分析,得出了多项式的每一项的节点表示。
那么就可以把每一项以节点的形式连接在链表中了。
算法详解:
![](https://i-blog.csdnimg.cn/blog_migrate/83cf6f691cfb8edcb9c1b16bc2e9fcb8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/246dbc7b7f44550e7681dd7dc01cceec.png)
![](https://i-blog.csdnimg.cn/blog_migrate/df68aeb8b56cf1f020bd19df8cafb32e.png)
补充
-
单链表基本操作:
-
(1)头插法建立单链表
算法思想:
该方法从一个空表开始,生成新的节点,并将读取到的数据存放在新节点的数据域中,然后将新节点插入到当前链表的表头,即头节点之后。读者可以画出头插法示意图,以便于理解。
算法如下:
// // main.cpp // dataStruct // // Created by 海岩 on 2019/9/13. // Copyright © 2019 海岩. All rights reserved. // #include <iostream> using namespace std; typedef struct LNode{ int data; struct LNode *next; }LNode,*LinkList; LinkList Create_HeadList(); bool display_List(LinkList L); int main(int argc, const char * argv[]) { LinkList L; L = Create_HeadList(); if(display_List(L)) cout<<"该表为空,无法输出。"; cout<<'\n'; return 0; } LinkList Create_HeadList(){ //头插法创建单链表 LinkList L = new LNode; L->next = NULL; LNode *s; int data; cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; while(data!=999){ s = new LNode; s->data = data; s->next = L->next; //建立单链表的关键在于如何连接节点 L->next = s; //注意画出示意图,以防出错 cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; } return L; } bool display_List(LinkList L){ LNode *cheak = L->next; if(cheak == NULL) return true; // 如果链表为空,在main函数内给出提示。 while(cheak!=NULL){ cout<<cheak->data<<"->"; cheak = cheak->next; } return false; }
运行结果:
-
(2)尾插法建立单链表
算法思想:
头插法虽然简单,但生成的链表中节点的次序跟输入次序不一致。若希望次序一致,可采用尾插法。
该方法是将新节点插入到当前链表的表尾上,为此必须增加一个尾指针r,使其始终指向当前链表的表尾。算法如下:
// // main.cpp // dataStruct // // Created by 海岩 on 2019/9/13. // Copyright © 2019 海岩. All rights reserved. // #include <iostream> using namespace std; typedef struct LNode{ int data; struct LNode *next; }LNode,*LinkList; LinkList Create_EndList(); bool display_List(LinkList L); int main(int argc, const char * argv[]) { LinkList L; L = Create_EndList(); if(display_List(L)) cout<<"该表为空,无法输出。"; cout<<'\n'; return 0; } LinkList Create_EndList(){ //尾插法创建单链表 LinkList L = new LNode; L->next = NULL; LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点 r = L; int data; cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; while(data!=999){ s = new LNode; s->data = data; r->next = s; r = s; //r再次指向新的表尾节点 cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; } r->next = NULL; return L; } bool display_List(LinkList L){ LNode *cheak = L->next; if(cheak == NULL) return true; // 如果链表为空,在main函数内给出提示。 while(cheak!=NULL){ cout<<cheak->data<<"->"; cheak = cheak->next; } return false; }
运行结果:
-
(3)按序号查询节点值
算法思想:
在单链表中从第一个节点出发,顺指针next域逐个往下搜索,直到找到第i个节点为止,否则返回最后一个节点指针域NULL。
算法如下:// // main.cpp // dataStruct // // Created by 海岩 on 2019/9/13. // Copyright © 2019 海岩. All rights reserved. // #include <iostream> using namespace std; typedef struct LNode{ int data; struct LNode *next; }LNode,*LinkList; LinkList Create_EndList(); bool display_List(LinkList L); bool seek_index(LinkList L,int index); int main(int argc, const char * argv[]) { LinkList L; int index; L = Create_EndList(); if(display_List(L)) cout<<"该表为空,无法输出。"<<'\n'; cout<<"输入查询的位置:"; cin>>index; if(seek_index(L,index)) cout<<"查询出错。"; cout<<'\n'; return 0; } LinkList Create_EndList(){ //尾插法创建单链表 LinkList L = new LNode; L->next = NULL; LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点 r = L; int data; cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; while(data!=999){ s = new LNode; s->data = data; r->next = s; r = s; //r再次指向新的表尾节点 cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; } r->next = NULL; return L; } bool display_List(LinkList L){ LNode *cheak = L->next; if(cheak == NULL) return true; // 如果链表为空,在main函数内给出提示。 while(cheak!=NULL){ cout<<cheak->data<<"->"; cheak = cheak->next; } cout<<'\n'; return false; } bool seek_index(LinkList L,int index){ int i=1; LNode *cheak = L->next; if(cheak == NULL){ cout<<"表为空。"<<'\n'; return true; } if(index == 0){ cout<<"你要查询的位置为头指针,没有数据。"<<'\n'; return true; } if(index < 0){ cout<<"你要查询的位置为负,此位置不存在。"<<'\n'; return true; } while(cheak&&i<index){ cheak = cheak->next; i++; } if(cheak == NULL){ cout<<"查找位置超出表长,此位置不存在"<<'\n'; return true; } cout<<"查找成功:"<<index<<"----"<<cheak->data<<'\n'; return false; }
运行结果:
总结:
其实算法实现所需功能并不难,难点在于如何成为一个好的算法。编写算法要注意思考:算法的正确性、可读性、健壮性、优化性。
就如本算法,你会发现有大量的if语句,其实这些都是在预防可能是程序崩溃的情况发生。
(1)表为空时,查找会出错。
(2)查找为位置为负,查找会出错。
(3)查找位置为零,查找会出错。
(4)查找位置超出表长,查找会出错。
除了要发现这些错误,还要给出错误的原因,这才有可能将初步算法优化为一个好的算法。
-
(4)按值查询表节点
算法思想:
从单链表的第一个节点开始,由前往后依次比较表中各节点数据域的值,若节点数据域的值等于给定的值data,则返回该节点的位置,若整个表中无这样的节点,则返回出错信息。算法如下:
// // main.cpp // dataStruct // // Created by 海岩 on 2019/9/13. // Copyright © 2019 海岩. All rights reserved. // #include <iostream> using namespace std; typedef struct LNode{ int data; struct LNode *next; }LNode,*LinkList; LinkList Create_EndList(); bool display_List(LinkList L); bool seek_data(LinkList L,int data); int main(int argc, const char * argv[]) { LinkList L; int data; L = Create_EndList(); if(display_List(L)) cout<<"该表为空,无法输出。"<<'\n'; cout<<"输入查询的节点值:"; cin>>data; if(seek_data(L,data)) cout<<"查询出错。"; cout<<'\n'; return 0; } LinkList Create_EndList(){ //尾插法创建单链表 LinkList L = new LNode; L->next = NULL; LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点 r = L; int data; cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; while(data!=999){ s = new LNode; s->data = data; r->next = s; r = s; //r再次指向新的表尾节点 cout<<"输入整数节点值(输入999代表结束输入):"; cin>>data; } r->next = NULL; return L; } bool display_List(LinkList L){ LNode *cheak = L->next; if(cheak == NULL) return true; // 如果链表为空,在main函数内给出提示。 while(cheak!=NULL){ cout<<cheak->data<<"->"; cheak = cheak->next; } cout<<'\n'; return false; } bool seek_data(LinkList L,int data){ int i=1; LNode *cheak = L->next; if(cheak == NULL){ cout<<"表为空。"<<'\n'; return true; } while(cheak&&cheak->data!=data){ cheak = cheak->next; i++; } if(cheak == NULL){ cout<<"查找的值不存在"<<'\n'; return true; } cout<<"查找成功:"<<cheak->data<<"--对应的位置为--"<<i<<'\n'; return false; }
运行结果:
-
(5)插入节点操作
算法思想:
插入操作是将值为x的新节点插入到单链表的第i各位置上。先检查插入位置的合法性,然后找到待插入位置的前驱节点,即第i-1各节点,再在其后插入新节点。算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
LNode *GetElem(LinkList L,int index);
bool insert_index(LinkList L,int index,int data);
int main(int argc, const char * argv[]) {
LinkList L;
int index;
int data;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"输入要插入的节点位置:";
cin>>index;
cout<<"输入要插入的节点值:";
cin>>data;
if(insert_index(L,index,data))
cout<<"插入出错。";
cout<<'\n';
return 0;
}
LinkList Create_EndList(){
//尾插法创建单链表
LinkList L = new LNode;
L->next = NULL;
LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点
r = L;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
r->next = s;
r = s; //r再次指向新的表尾节点
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
r->next = NULL;
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
LNode *GetELem(LinkList L,int index){
int i=0;
LNode *cheak = L;
if(index < 0){
cout<<"位置序号从1开始,位置不合法"<<'\n';
return NULL;
}
while(cheak&&i<index){
cheak = cheak->next;
i++;
}
if(cheak == NULL){
cout<<"插入位序过大,位置不合法"<<'\n';
return NULL;
}
return cheak;
}
bool insert_index(LinkList L,int index,int data){
LNode *p;
LNode *s = new LNode;
s->data = data;
p = GetELem(L, index-1);
if(p == NULL)
return true;
s->next = p->next;
p->next = s;
cout<<"插入成功"<<'\n';
display_List(L);
return false;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/b75d6440b3cfebcee86717946ef528e2.png)
算法思想:
删除操作是将单链表的第i各节点删除。先检查删除位置的合法性,然后查找表中的第i-1个节点,即被删除节点的前驱节点,再将其删除。并说明删除的值。
算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
LNode *GetElem(LinkList L,int index);
bool del_index(LinkList L,int index);
int main(int argc, const char * argv[]) {
LinkList L;
int index;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"输入要删除的节点位置:";
cin>>index;
if(del_index(L,index))
cout<<"删除出错。";
cout<<'\n';
return 0;
}
LinkList Create_EndList(){
//尾插法创建单链表
LinkList L = new LNode;
L->next = NULL;
LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点
r = L;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
r->next = s;
r = s; //r再次指向新的表尾节点
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
r->next = NULL;
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
LNode *GetELem(LinkList L,int index){
int i=0;
LNode *cheak = L;
if(index < 0){
cout<<"位置序号从1开始,位置不合法"<<'\n';
return NULL;
}
while(cheak&&i<index){
cheak = cheak->next;
i++;
}
if(cheak->next == NULL){
cout<<"删除位序过大,位置不合法"<<'\n'; //注意这里如何判断位序过大,和插入位序不同之处
return NULL;
}
return cheak;
}
bool del_index(LinkList L,int index){
int data;
LNode *p;
LNode *s;
p = GetELem(L, index-1);
if(p == NULL)
return true;
s = p->next;
p->next = s->next;
data = s->data;
free(s);
cout<<"删除成功"<<'\n';
cout<<"在"<<index<<"位置,删除了"<<data<<'\n';
display_List(L);
return false;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/2ec483ca0f97f465b54038ece8af57b0.png)
算法思想:
给定一个节点值,对单链表逐个对比,找到这个值,同时再找到它的前驱节点,然后再删除它。如果找不到改值,则说名出错信息。
算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
LNode *GetElem(LinkList L,int data);
bool del_index(LinkList L,int index);
int main(int argc, const char * argv[]) {
LinkList L;
int data;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"输入要删除的节点值:";
cin>>data;
if(del_index(L,data))
cout<<"删除出错。";
cout<<'\n';
return 0;
}
LinkList Create_EndList(){
//尾插法创建单链表
LinkList L = new LNode;
L->next = NULL;
LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点
r = L;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
r->next = s;
r = s; //r再次指向新的表尾节点
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
r->next = NULL;
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
LNode *GetELem(LinkList L,int data){
LNode *cheak = L->next;
LNode *cheak0 = L;
if(cheak == NULL){
cout<<"表为空。"<<'\n';
return NULL;
}
while(cheak&&cheak->data!=data){
cheak = cheak->next;
cheak0 = cheak0->next;
}
if(cheak->next == NULL){
cout<<"删除的值不存在。"<<'\n';
return NULL;
}
return cheak0;
}
bool del_index(LinkList L,int data){
LNode *p;
LNode *s;
p = GetELem(L, data);
if(p == NULL)
return true;
s = p->next;
p->next = s->next;
free(s);
cout<<"删除成功"<<'\n';
display_List(L);
return false;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/938eb91437c29c25c108b4cbc3435630.png)
算法思想:
遍历所有节点,并计数。
算法如下:
//
// main.cpp
// dataStruct
//
// Created by 海岩 on 2019/9/13.
// Copyright © 2019 海岩. All rights reserved.
//
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList Create_EndList();
bool display_List(LinkList L);
int lengthList(LinkList L);
int main(int argc, const char * argv[]) {
LinkList L;
L = Create_EndList();
if(display_List(L))
cout<<"该表为空,无法输出。"<<'\n';
cout<<"表长为:"<<lengthList(L)<<'\n';
cout<<'\n';
return 0;
}
LinkList Create_EndList(){
//尾插法创建单链表
LinkList L = new LNode;
L->next = NULL;
LNode *s,*r; //r指针用来标记表尾,r始终指向表尾节点
r = L;
int data;
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
while(data!=999){
s = new LNode;
s->data = data;
r->next = s;
r = s; //r再次指向新的表尾节点
cout<<"输入整数节点值(输入999代表结束输入):";
cin>>data;
}
r->next = NULL;
return L;
}
bool display_List(LinkList L){
LNode *cheak = L->next;
if(cheak == NULL)
return true; // 如果链表为空,在main函数内给出提示。
while(cheak!=NULL){
cout<<cheak->data<<"->";
cheak = cheak->next;
}
cout<<'\n';
return false;
}
int lengthList(LinkList L){
int i = 0;
LNode *p = L->next;
while(p){
i++;
p = p->next;
}
return i;
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/91ca79e3355524c9991160573f7a4795.png)