第二章 线性表
2.1 线性表的定义和基本操作
2.1.1 线性表的定义
线性表是具有相同数据类型的n个数据元素的有限序列。其中n为表长。
其中,线性表的第一个元素称为表头元素,最后一个元素称为表尾元素,除了第一个元素外,线性表中每一个元素都有直接后继,除了最后一个元素外,线性表中每一个元素都有直接前驱。
2.1.2 线性表的基本操作
InitList(&L):初始化表。构造一个空的线性表。
DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
LocateElem(L,e):按值查找操作。在表中L查找具有给定关键字值的元素。
GetELem(L):按位查找操作。获取表L中第i个位置的元素的值。
Listlnsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e.
LlstDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值。
Empty(L):判空操作。若l为空表,则返回TRUE,否则返回FALSE。
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
2.2 线性表的顺序表示
顺序表的实现–静态分配
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];//数据元素类型
int length;
}SqList;
//基本操作——初始化一个顺序表
void InitList(SqList &L){
for(int i = 0;i < MaxSize;i++)
L.data[i] = 0;
L.length = 0;
}
int main(){
SqList L;//先声明一个顺序表
InitList(L);
}
顺序表的实现–动态分配
#define InitSize 10//顺序表的初始长度
#define <stdlib.h>
typedef struct{
ElemType *data;//指示动态分配数组的指针
int MaxSize;
int length;
}SeqList;
void InitList(SeqList &L){//初始化
L.data = (int *)malloc(InitSize*sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
void IncreaseSize(SeqList &L, int len){//动态扩容
int *p = L.data;
L.data = (int *)malloc((L.MaxSize + len)*sizeof(int));
for(int i = 0;i<L.length;i++){
L.data[i] = p[i];//将数据复制到新区域
}
L.MaxSize = L.MaxSize+len;
free(p);
}
顺序表的实现–插入操作
bool listInsert(SeqList &L, int i, ElemType e) {
if (i < 1 || i > L.maxSize + 1) {
return false;
}
if (L.length >= MaxSize) {
return false;
}
//将第i个元素及之后的元素右移
for (int j = L.length; j >= i; j--) {
L.data[j] = L.data[j - 1];
}
L.data[i - 1] = e;
L.length++;
return true;
}
顺序表的实现–删除操作
bool listDelete(SeqList &L,int i,ElemType &e){
if (i<1 || i>=L.length + 1) return false;
if(L.length >= L.maxSize) return false;
e = L.data[i-1];
for (int j = i; j < L.length; ++j) {
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}
顺序表的实现–尾插法
void listInsert(SeqList &L, ElemType e) {
L.data[L.length] = e;
L.length++;
}
顺序表的实现–按值查找操作
ElemType LocateElem(SeqList L,ElemType e){
for (int i = 0; i < L.length; ++i) {
if (L.data[i] == e){
return i+1;
}
}
return -1;
}
main函数
int main() {
initList(L);
listInsert(L, 0);
listInsert(L, 1);
listInsert(L, 2);
listInsert(L, 3);
listInsert(L, 4);
// int p;
// bool flag = listDelete(L,2,p);
// std::cout<< flag << std::endl;
printf("%d",LocateElem(L,3));
for (int i = 0; i < L.length; ++i) {
printf("%d\n", L.data[i]);
}
return 0;
}
特点:
- 随机访问
- 存储密度高
- 拓展容量不方便
- 插入删除元素不方便
2.3 线性表的链式表示
2.3.1 单链表的定义
线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。
2.3.1 单链表的实现
定义头文件
#ifndef DATASTRUCTURE_SINGLELINKEDLIST_H
#define DATASTRUCTURE_SINGLELINKEDLIST_H
#define ElemType int
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkedList;
#endif //DATASTRUCTURE_SINGLELINKEDLIST_H
单链表的实现–初始化
bool InitList(LinkedList &L){
L = (LNode *)malloc(sizeof(LNode));
if (L== nullptr)return false;
L->next = nullptr;
return true;
}
单链表的实现–头插法
LinkedList List_HeadInsert(LinkedList &L){
LNode *s;int x;
L = (LinkedList)malloc(sizeof(LNode));//创建头结点
L->next = nullptr;
scanf("%d",&x);
while (x!=9999){
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d",&x);
}
return L;
}
单链表的实现–尾插法
LinkedList List_TailInsert(LinkedList &L){
int x;
LNode *s, *r = L;
L = (LinkedList)malloc(sizeof(LNode));//创建头结点
s = (LNode *)malloc(sizeof(LNode));
scanf("%d",&x);
while (x!=9999){
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d",&x);
}
r->next = nullptr;
return L;
}
Note:使用LinkList强调这是一个单链表,使用LNode * 强调这是一个结点
单链表的实现–按位序插入
bool ListInsert(LinkedList &L,int i,ElemType e){
if (i<1) return false;
LNode *p;
int j = 0;//当前p指向的是第几个结点
p = L;
while (p!= nullptr && j < i-1){
p = p->next;
j++;
}
if (p== nullptr)return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
单链表的实现–指定结点的前插操作
bool InsertPriorNode(LNode *p,LNode *s){
if (p == nullptr || s == nullptr)return false;
s->next = p->next;
p->next = s;
ElemType temp = p->data;
p->data = s->data;
s->data = temp;
return true;
}
单链表的实现–指定结点的后插操作
bool InsertNextNode(LNode *p,ElemType e){
if (p== nullptr)return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
单链表的实现–按位序删除
bool ListDelete(LinkedList &L,int i,ElemType &e){
if (i<1) return false;
LNode *p;
int j = 0;
p = L;
while (p!= nullptr && j < i-1){
p = p->next;
j++;
}
if (p== nullptr || p->next == nullptr)return false;
LNode *q = p->next;
e = q->data;
p->next = q->next;
free(q);
return true;
}
单链表的实现–指定结点删除
bool DeleteNode(LNode *p){
if (p== nullptr)return false;
LNode *q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);
return true;
}
单链表的实现–按位查找
LNode * GetElem(LinkedList L,int i){
int j = 1;
LNode *p = L->next;
if (i == 0)return L;
if (i < 1) return nullptr;
while (p!= nullptr && j<i){
p = p->next;
j++;
}
return p;
}
单链表的实现–按值查找
LNode * LocateElem(LinkedList L,ElemType e){
LNode *p = L->next;
while (p!= nullptr && p->data != e){
p = p->next;
}
return p;
}
带头结点和不带头结点的比较:
不带头结点:写代码麻烦!对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑,对空表和非空表的处理也需要用不同的代码逻辑; 头指针指向的结点用于存放实际数据;
带头结点:头指针指向的头结点不存放实际数据,头结点指向的下一个结点才存放实际数据;
2.3.2 双链表的实现
双链表的实现–初始化双链表
bool InitDLinkList(DLinklist &L){
L = (DNode *)malloc(sizeof(DNode));
if (L== nullptr)return false;
L->prior = nullptr;
L->next = nullptr;
return true;
}
双链表的实现–在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
s->next = p->next;
if (p->next != nullptr)p->next->prior = s;
s->prior = p;
p->next = s;
}
双链表的实现–双链表的删除
bool DeleteNextDNode(DNode *p){
if (p == nullptr) return false;
DNode *q = p->next;
if (q == nullptr) return false;
p->next = q->next;
if (q->next != nullptr)
q->next->prior = p;
free(q);
return true;
}
循环链表–循环单链表
最后一个结点的指针不是NULL,而是指向头结点
typedef struct LNode{
ElemType data;
struct LNode *next;
}DNode, *Linklist;
/初始化一个循环单链表
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点
if(L==NULL) //内存不足,分配失败
return false;
L->next = L; //头结点next指针指向头结点
return true;
}
//判断循环单链表是否为空(终止条件为p或p->next是否等于头指针)
bool Empty(LinkList L){
if(L->next == L)
return true; //为空
else
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){
if(p->next == L)
return true;
else
return false;
}
循环链表–循环双链表
表头结点的prior指向表尾结点,表尾结点的next指向头结点
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指向头结点
}
void testDLinkList(){
//初始化循环单链表
DLinklist L;
InitDLinkList(L);
//...
}
//判断循环双链表是否为空
bool Empty(DLinklist L){
if(L->next == L)
return true;
else
return false;
}
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L, DNode *p){
if(p->next == L)
return true;
else
return false;
}
静态链表
#define MaxSize 50
#define ElemType int
typedef struct {
ElemType data;
int next;
}SlinkList[MaxSize];