目录
一、线性表的定义
二、线性表的分类
(1)顺序表
(2)单链表
三、最常见的基本操作
四、C/C++实现
(1)顺序表
1、静态顺序表
1)定义其数据类型。
2)相关代码。
2、动态顺序表
1)定义其数据类型。
2)相关代码
(2)单链表
1、带头结点
1)初始化
2)判空
3)查找
4)插入
4)删除
2、不带头结点
1)初始化
2)判断是否为空
3)插入
(3)扩展
1、双链表
1)初始化
2)删除
3)销毁
2、循环单链表
1)初试化
3、循环双链表
1)初始化
五、知识点扩充
(1)"&"的意义
(2)自定义函数名要有可读性
(3)"typedef"
(4)"LNode" 和"LinkList *"
(5)适当使用"封装"
n个相同特性的数据元素的有限序列
1、特点:除第一个数据元素外,都有一个直接前驱;除最后一个数据元素外,都有一个直接后继
根据存储方式的不同,分为顺序表和单链表。
--逻辑上相邻的数据元素物理上也相邻
1、特点:
① 占用连续的存储空间;可以随机存取;空间压力较大
② 插入删除需要移动元素,故适合尾插、尾删
--逻辑上相邻的数据元素物理上可以不相邻,各结点间的关系用指针表示
2、特点:
① 占用离散的存储空间;不可以随机存取,只可顺序存取;空间压力较小
② 插入删除不需要移动元素;适合头插、头删
创建、销毁、增、删、改、查,其他还需根据实际需求定义基本操作。
根据操作实现方式的不同,分为静态顺序表和动态顺序表。
--“静态数组”实现
#define Max 10
typedef int ElemType;
typedef struct
{
ElemType data[Max];
int length;
}SqList; //顺序表的别名
#include
#include
#define Max 25
typedef int ElemType;
//顺序表
typedef struct
{
ElemType data[Max];
int length;
}SqList;
//初始化
void InitList(SqList &L)
{
for(int i=0;iL.length +1) //判断 i 的范围是否有效
{
printf("插入位置不合法!\n");
return false;
}
if(L.length>=Max) //空间已满,不能插入
{
printf("位序%d不合法,插入失败!\n",i);
return false;
}
for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
{
L.data[j]=L.data[j-1];
}
L.data[i-1]=e; //在位置i出放入e
L.length++; //长度加1
return true;
}
//删除
bool ListDelete(SqList &L,int i,int e)
{
if(i<1||i>L.length) //判断 i 的范围是否有效
{
printf("位序%d不合法,删除失败!\n",i);
return false;
}
e=L.data[i-1]; //将被删除的元素赋值给e
for(int j=i;jL.length)
{
printf("位序%d不合法,查找失败!\n",i);
return false;
}
printf("第%d个位序为:%d\n",i,L.data[i-1]);
return true;
}
//打印
void PrintList(SqList L)
{
printf("当前顺序表数据依次为:");
for(int i=0;i
--“动态数组”实现:需要动态申请内存空间
C语言:用malloc或者realloc函数申请空间 / free函数释放空间
C++:new关键字申请空间 / delete关键字删除
typedef struct
{
ElemType *data; //动态分配数组的指针
int MAX; //顺序表的最大容量
int length; //顺序表的当前长度
}SqList;
//初始化
void InitList(SqList &L)
{
L.data=(int *)malloc(M*sizeof(int)); // malloc函数申请空间
L.length=0;
L.MAX=M;
}
//增加长度
void IncreaseSize(SqList &L,int len)
{
int *p=L.data ;
L.data=(int *)malloc((L.MAX+len)*sizeof(int)); // malloc函数申请空间
for(int i;i
数据类型:
typedef int ElemType;
typedef struct LNode
{
ElemType data; //每个节点存放一个数据元素
struct LNode *next; //指针指向下一个节点
}LNode,*LinkList;
根据实现方式的不同,分为带头结点和不带头结点;但就方便程度,多数采用带头结点的方式。
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode)); //分配一个头结点
if(L==NULL)
{
return false;
}
L->next = NULL;
return true;
}
bool Empty(LinkList L)
{
if(L->next == NULL)
{
return true;
}
else
{
return false;
}
}
--按位查找,返回第 i 个元素的结点
LNode *GetElem(LinkList L,int i)
{
if(i<0)
{
return NULL;
}
LNode *p; //当前p指向扫描到的结点
int j=0; //当前p指向的第几个结点
p=L; //p指向 L所指向的头结点,头结点是第0个结点(不存数据)
while(p!=NULL && jnext;
j++;
}
return p;
}
--按值查找,找到数据域为e的结点
LNode *LocateElem(LinkList L,ElemType e)
{
LNode *p=L->next; //从第 i 个结点开始查找数据域为 e 的结点
while(p!=NULL && p->data!=e)
{
p=p->next;
}
return p; //找到后返回该结点指针,否则返回NULL
}
①前插操作:在 p 结点之前插入元素 e
bool InserPriorNode(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 连到 p 之后
s->data=p->data; //将 p 中元素复制到 s 中
p->data=e; //p 中元素覆盖为 e
return true;
}
②后插操作:在第 i 个位置插入元素 e
bool ListNextInsert(LinkList &L,ElemType e)
{
if(p==NULL) //i 值超出“单链表长度(包括头结点)”的长度
{
return false;
}
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s; //将结点 s 连到 p 之后
return true ; //插入成功
}
--插入。
bool ListInsert(LinkList &L,int i,ElemType e)
{
if(i<1)
{
return false;
}
LNode p;
p=GetElem(L,i-1); //找到第 i-1 个结点
return ListNextInsert(p,e); //调用后插函数
}
①删除位序 i 的结点,并返回其数据域的值。
bool ListDelete(LinkList &L,int i,ElemType &e)
{
if(i<1)
{
return false;
}
LNode *p; //当前p指向扫描到的结点
int j=0; //当前p指向的第几个结点
p=L; //p指向 L所指向的头结点,头结点是第0个结点(不存数据)
while(p!=NULL && jnext;
j++;
}
if(p==NULL) //i 值超出“单链表长度(包括头结点)”的长度
{
return false
}
LNode *q=p->next; //令q指向被删除结点
e=q->data; //用 e 返回元素的值
p->next =q->next;
free(q);
return true;
}
②删除指定结点 p
bool DeleteNode(LNode *p)
{
if(p==NULL)
{
return false;
}
LNode *q=p->next; //令q指向*p的后继结点
p->data =p->next->data; //p与后继结点*q交换数据与
p->next =q->next ;//将*q结点从链中“断开 ”
free(q);//释放后继结点的存储空间
return true;
}
bool InitList(LinkList &L)
{
L=NULL;
return true;
}
bool Empty(LinkList L)
{
if(L==NULL)
{
return true;
}
else
{
return false;
}
}
bool LinkList(LinkList &L,int i,ElemType e)
{
if(i<1)
{
return false;
}
if(i==1) //特殊情况:插入到第一个结点前
{
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s; //头指针指向新结点
return true;
}
LNode *p; //指针 p 指向当前扫描到的结点
int j=1; //当前 p 指向的是第几个结点
p = L;//p指向第1个结点(注意:不是头结点)
while(p!=NULL && jnext ;
j++;
}
if(p=NULL) //i 值超过“单链表长度+1 ”的长度
{
return false;
}
LNode *s=(LNode *)malloc(sizeof(LNOde));
s->data =e;
s->next =p->next ;
p->next =s;
return true; //插入成功
}
-- 以下链表都是带头结点的情况
--除第一个结点和最后一个结点外,每一个结点都有一个直接前驱指针和一个直接后继指针分别于上一个结点和下一个结点相连。
typedef int ElemType;
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L)
{
L=(DNode *)malloc(sizeof(DNode));//分配一个头结点
if(L==NULL)
{
return false;
}
L->prior=NULL; //头结点的prior永远指向NULL
L->next=NULL;//头结点之后暂未有结点
return true;
}
--删除p结点的后继结点q
bool DeleteNextDNode(DNode *p)
{
if(p==NULL)
{
return false;
}
DNode *q=p->next ;//找到p的后继结点q
if(q==NULL)
{
return false;
}
p->next =q->next;
if(q->next !=NULL) //q结点不是最后一个结点
{
q->next->prior =p;
}
free(q);
return true;
}
void DestoryList(DLinkList &L)
{
while(L->next!=NULL) //循环释放各个数据结点
{
DeleteNextDNode(L);
}
free(L); //释放头结点
}
-- 每个结点都有一个直接后继指针与下一个结点相连。
typedef int ElemType;
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
bool InitLinkList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
{
return false;
}
L->next =L; //头结点的next指向头结点
return true;
}
-- 每一个结点都有一个直接前驱指针和一个直接后继指针分别于上一个结点和下一个结点相连。
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L)
{
L=(DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L==NULL)
{
return false;
}
L->prior=L; //头结点的 prior 指向头结点
L->next=L; //头结点的next 指向头结点
return true;
}
--如,在创建函数CreateList(SqList &L)中, “&”的使用——对参数的修改结果需要传递回来。
--如,不恰当的用A(SqList &L,int i,int e)替代ListDelete(SqList &L,int i,int e)。
--typedef <数据类型> <别名> ,如typedef int ElemType —— ElemType 具有与 int 一样的功能
--LNode* GetElem(LinkList L,int i) ,其中LinkList:强调这是一个单链表;LNode*——强调这是一个结点;但两者使用效果等价。
--“封装”即不同操作间有相同的部分,将相同的操作提取,单独定义一个函数,后期如要使用,只需调用它即可,这样就避免了代码的重复性,较为简洁、也易维护系统。