一、线性表概述
(一)线性表的定义
- 线性表是n个数据元素的有限序列。表中元素存在线性关系。根据它们之间的关系可以构成一个线性序列,记作
(a1,a2,...an)
- 线性表中的元素具有相同的数据类型。线性表中数据元素的个数n定义为线性表的长度,称为表长。n=0时成为空表。ai是第i个数据元素,称i为位序。
(二)线性表的主要操作
- 线性表有以下基本操作
1)初始化函数:void intList(List&L)
先决条件:线性表L不存在
操作结果:构造一个空的线性表L
2)清空函数:void clearList(List&L)
先决条件:线性表L已经存在
操作结果:将线性表L清空
3)计算表长函数:int Length(List&L)
先决条件:线性表L已经存在
操作结果:返回第i个元素的位置
4)定位函数:position Locate(List&L,int i)
先决条件:线性表L存在且整数i属于[1,n]
操作结果:返回第i个元素的位置
5)查找函数:position Search(List&L,DateType x)
先决条件:线性表L已经存在,且x的数据类型与表元素的数据类型相同
操作结果:线性表中查找与x匹配的元素,查找成功返回元素位置,失败返回失败位置
6)插入函数:bool Insert(List&L,int i,DateType x)
先决条件:线性表L存在,且整数i属于[1,n+1],且x的数据类型与表中相同
操作结果:将元素x插入线性表L第i个位置。函数返回插入成功与否的标志。
7)删除函数:bool Remove(List&L,int i,DateType&x)
先决条件:线性表L存在,且整数i属于[1,n]
操作结果:从线性表中移除第i个元素。删除成功则通过引用型参数x得到删除值,函数返回删除成功的标志。
8)遍历函数:void Traverse(List&L)
先决条件:线性表L已经存在。
操作结果:依次访问线性表L中所有元素,且仅访问一次
9)复制函数:void Copy(List&L1,List&L2)
先决条件:线性表L1和L2已存在,且L1为空
操作结果:复制L2所有元素到L1
10)排序函数:void Sort(List&L)
先决条件:线性表L存在
操作结果:对线性表中所有元素L排序。
二、顺序表
- 线性表的存储方式有两种:顺序和链表。用顺序存储方式实现的线性表称为顺序表,用一维数组作为其存储结构。
(一)顺序表的定义和特点
1、顺序表的定义
- 把线性表中的所有元素按照其逻辑顺序依次存储在一块连续的存储空间中,就得到顺序表。设顺序表中的每个元素的数据类型为DateType,则每个元素所占字节数为sizeof(DateType),整个顺序表所占字节数为n x sizeof(DateType),n为线性表长度。
2、顺序表特点
- 顺序表中元素的逻辑顺序与存放的物理顺序一致,比如第i个元素存储在第i-1个物理位置
- 对顺序表中的元素可以从首元开始顺序访问,也可以按照下标随机访问
- 顺序表可以用C的一维数组实现
- 线性表存储的数组类型就是顺序表中元素的数据类型,数组包含的元素个数要大于或者等于顺序表长度。
(二)顺序表的结构定义
1、静态存储表示
- 存储线性表的一维数组大小maxSize和空间data在结构声明中明确指定
#include<stdlib.h>
#include<stdio.h>
#define maxSize 100 //顺序表存储的最大容量
typedef int DataType;//每个元素的数据类型
typedef struct{
DataType data[maxSize];//线性表的存储数组
int n;//数组中当前已有表的元素数
}SeqList;//创建一个新表
2、动态存储表示
- 存储空间通过malloc动态分配,若存储空间已满可另外分配一块更大的代替。
- 表示数组大小的maxSize放在顺序表的结构内定义
//定义在SeqList.cpp
#include<stdio.h>
#include<stdlib.h>
#define intSize 30//表的初始大小
typedef int DataType;//预设表的元素数据类型
typedef struct {
DataType *data;//元素存储向量的指针
int maxSize,n;//表的最大长度和当前元素个数
}SeqList;
- 顺序表的主要操作
#include "SeqList.h"
#include<stdio.h>
#include<stdlib.h>
//1、初始化顺序表
void intList(SeqList&L){
L.data=(DataType*)malloc(initSize*sizeof(DataType));//创建存储数组
if(!L.data){
printf("存储分配错误!\n");//分配失败
}
L.maxSize=initSize;
L.n=0;//置表的实际长度为0
}
//2、清空顺序表
void clearList(SeqList&L){
L.n=0;
}
//3、计算顺序表长度
int Length(SeqList&L){
return L.n;
}
//4、盘表空否,空返回true,否则false
bool isEmppty(SeqList&L){
return L.n==0;
}
//5、判断满否,满返回true,否则false
bool isFull(SeqList&L){
return L.n==L.maxSize;
}
//6、从数组A[n]创建顺序表。存储分配失败返回false,否则true
bool creatList(SeqList&L,DataType A[],int n){
L.data=(DataType*)malloc(initSize*sizeof(DataType));//创建存储数组
if(!L.data)
return false;//分配失败
for(int i=0;i<n;i++)
L.data[i]=A[i];
L.maxSize=initSize;
L.n=n;
return true;//分配成功,传送数据
}
//7、在表中顺序查找与x匹配的元素,查找成功返回该元素所在位置,否则函数返回-1
int Search(SeqList&L,DataType x){
for(int i=0;i<L.n;i++)
if(L.data[i]==x) return i;//顺序查找,找到则返回元素位置
return -1;//查找失败,返回-1
}
//8、 函数返回第i个元素的位置物理位置。如果函数返回-1,表示定位失败
int Locate(SeqList&L,int i){
if(i>0&&i<=L.n) return i-1; //i合法返回i-1
else return -1;//定位失败,返回-1
}
//9、将新元素插入表中第i个元素,若成功返回true,否则false
bool Insert(SeqList&L,int i,DataType x){
if(L.n==L.maxSize) return false;//表满,不能插入
if(i<=0||i>L.n+1) return false;//参数i不合理
for(int j=L.n-1;j>=i-1;j--)L.data[j+1]=L.data[j];//依次后裔,空出第i号位置
L.data[i-1]=x; L.n++;//插入,表长度+1
return true;
}
//10、删除顺序表第i个元素,通过引用参数x返回删除元素的值。删除成功返回true,否则false
bool Remove(SeqList&L,int i,DataType x){
if(!L.n) return false;//表空,不能删除
if(i<=0||i>L.n) return false;//参数i不合理,不能删除
x=L.data[i-1];//存储被删除的元素值
for(int j=i;j<L.n;j++)L.data[j-1]=L.data[j];//依次前移,填补;
L.n--; return true;//表长度-1
}
//11、顺序表遍历
void printList(SeqList&L){
for(int i=0;i<L.n;i++)
printf("%d,",L.data[i]);
}
3、顺序表的应用
- 两个顺序表的并和交
#include"List.h"
#include<stdio.h>
#include<stdlib.h>
//1、集合的并运算
void Merge(SeqList&LA,SeqList&LB){
DataType x;
int n=Length(LA);
int m=Length(LB);
int i,k;
for(i=0;i<m;i++){//检查LB中所有元素
x=LB.data[i];//在LB中取第i个元素,值为x
k=Search(LA,x);//在LA中查找它
if(k==-1)
{
Insert(LA,n,x);//若在LA中未查到则插入它,插入到第n个元素之后
n++;
}
}
}
//2、集合的交运算,并将结果存于LA中
void Intersection(SeqList&LA,SeqList&LB){
int n=Length(LA);
int m=Length(LB);
int i=1,k;
DataType x;
while(i<=n){
x=LA.data[i];//在LA中取一元素
k=Search(LB,x);//在LB中查找它
if(k==-1){
Remove(LA,i,x);//若在LB中未找到则从LA中删除它
n--;
}
else
i++;
}
}
(三)单链表
1、单链表的定义和特点
- 定义
单链表是线性表的链接存储表示。单链表为数据元素附加了一个链接指针并形成一个一个的结点。结点由data(数据域,存放数据元素)和link(指针域/链域,存放用于记录下一个结点开始存储地址的指针)组成。链表的首元结点地址通过头指针first找到,其余存储在前驱节点的link域中。链表最后一个结点link域中存放空指针NULL。遍历链表时必须根据头指针找到首元节点。
- 特点
- 数据元素的逻辑顺序与物理顺序可能不一致。
- 单链表长度可以扩充。
- 对链表的遍历和查找只能从头指针指示的首元节点开始。
- 执行插入或者删除操作时,只需修改相关结点指针域即可。
2、单链表的结构定义
- 一个单链表包含零个或多个结点,因此一个类型为LinkList的单链表对象包含由零个或多个类型为LinkNode的链表结点。
- 链表的结构定义是一个递归定义,结点定义使用了一个指针,shi
#include<stdio.h>
#include<stdlib.h>
typedef int DataType;//表中元素数据类型定义
typedef struct node{
DataType data;//结点保存的元素数据
struct node*link;//连接指针
}LinkNode ,*LinkList;
3、单链表中的插入
单链表中插入算法有三种
- 头插法:新结点newNode插入在原首元结点之前。此时必须修改链表头指针first。
newNode->link=first;
first=newNode;
- 中间结点插入:首先让一个检测指针p指向第i-1个结点,再将新节点newNode插入p所指结点之后,新节点后面链接到原第i个结点。此时需要修改两个指针。
newNode->link=p->link;
p->link=newNode;
- 尾插法:检测指针p指向表中最后一个节点,再修改两个指针。
newNode->link=p->link;
p->link=newNode;
- 链表的中间插入与尾插法运算相同,这两种情况可以考虑合并。
- 算法实现
#include"LinkList.h"
#include<stdio.h>
#include<stdlib.h>
bool Insert(LinkList&first,int i,DataType x){
//将新元素x插入到第i个结点的位置。i从1开始,若i=1插入到原首结点之前
//若i的值过大,插入到链尾,函数返回成功与否标志。
if(first==NULL||i==1){//插入到空表/非空表首元节点前
LinkNode*newNode=(LinkNode*)malloc(sizeof(LinkNode));//建立新节点
newNode->data=x;
newNode->link=first; first=newNode;//新结点成为首元结点
}
else{//插入到链表中间或者尾部
LinkNode*p=first,*pr; int k=1;//从首元结点开始检测
while(p!=NULL&&k<i-1)//循环找到第i-1个结点
{
pr=p;p=p->link;k++;
}
if(p==NULL&&first!=NULL)p=pr;//链太短,p回到表尾
LinkNode*newNode=(LinkNode*)malloc(sizeof(LinkNode));//建立新结点
newNode->data=x;
newNode->link=p->link; p->link=newNode;//插入到*p之后
}
return true;//正常插入
}
4、单链表中的删除
根据删除位置不同有两种情况
- 删除首元结点
q=first;
first=first->link;
free(q);
- 删除中间结点或者尾部
q=p->link;
p->link=q->link;
free(q);
- 删除算法如下
#include"Linklist.h"
#include<stdio.h>
#include<stdlib.h>
bool Remove(LinkList&first,int i,DataType x){
//将链表中的第i个元素删除,i从1开始,x返回被删除的元素值
LinkNode*p,*q; int k;
if(i==0){
printf("%d是无效位置!\n",i);
return false;
}
else if(i==1){//删除首元结点时表头退到下一位
q=first;
first=first->link;
free(q);
}
else{//删除中间结点
p=first; k=1;
while(p!=NULL&&k<i-1){//循环链表找到第i-1个结点
p=p->link;
k++;
}
if(p==NULL||p->link==NULL){
printf("%d是无效位置!\n",i);
return false;
}//空表或者链太短
q=p->link;
p->link=q->link;
}
x=q->data; free(q);//取出被删除结点中数据值
return true;
}
5、带头结点的单链表
- 头结点位于链表首元结点之前。头结点的data域可以不存储任何信息,也可以存放一个特殊标志或表长。
- 将头结点当作第0个结点,在查找第ai-1个结点时从k=0开始,若i不超过表的长度+1,总能找到含ai-1的结点并让p指向它。.
- 单链表常用操作
//1、初始化单链表,建立只有头结点的空链表
void initList(LinkList&first){
first=(LinkNode*)malloc(sizeof(LinkNode));//创建头结点
first->link=NULL;//置空
}
//2、清空单链表仅保留链表的头结点
void clearList(LinkList&first){
LinkNode*p;
while(first->link!=NULL){
LinkNode*q=first->link; first->link=q->link;//当链不空时,删去链中所有结点,仅保留头结点
free(q);
}
}
//3、计算表的长度,函数返回表的长度
int Length(LinkList&first){
LinkNode*p=first->link; int count=0;
while(p!=NULL){
p=p->link;
count++;//循链扫描,计算结点数
}
}
//4、在单链表中查找x元素,成功则返回地址值否则返回NULL
LinkNode*Search(LinkList&first,DataType x){
LinkNode*p=first->link;
while(p!=NULL&&p->data!=x)p=p->link;//循环逐个找含x结点
return p;
}
//5、对第i个结点定位。函数返回i的地址,若i<0或超出结点个数,则返回NULL
LinkNode*Locate(LinkList&first,int i){
if(i<0)return NULL;
LinkNode*p=first; int k=0;
while(p!=NULL&&k<i){
p=p->link;
k++;//循链找第i个结点,k做结点计数
}
return p;//若返回NULL,i过大
}
//6、将新元素x插入表中第i个结点的位置,函数返回插入成功与否标志
bool Insert(LinkList&first,int i, DataType x){
LinkNode*p=Locate(first,i-1);//定位于第i-1个结点
if(p==NULL)return false;
LinkNode*s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x; s->link=p->link; p->link=s; //将*s链接到*p之后
return true;
}
//7、将链表中第i个元素删去,引用参数x返回元素值。函数返回删除成功与否的标志
bool Remove(LinkList&first,int i,DataType&x){
LinkNode*p=Locate(first,i-1);//定位于第i-1个结点
if(p==NULL||p->link==NULL)return false;
LinkNode*q=p->link;
p->link=q->link;
x=q->data; free(q);
return true;
}
//8、输出带头结点的单链表
void printList(LinkList&first){
LinkNode*p;
for(p=first->link;p!=NULL;p=p->link){
printf("%d",p->data);
}
printf("\n");
}