线性表
线性表的定义
线性表是由同一类型数据元素构成的有序序列,这种逻辑结构就是线性结构。
线性表中元素个数称为线性表长度,长度为0称为空表,表的起始位置称为表头,表的结束位置称为表尾,某元素的后一个元素称为其直接后继,前一个元素称为其直接前驱。
线性表的实现方式(类型定义)
顺序存储
用连续的一块存储空间顺序存放各元素(eg. 数组)。其基本结构与要素可以如下定义:
typedef int Position;
typedef struct LNode *PtrToLNode;
struct LNode {
ElementType Data[MAXSIZE];
Position Last;
};
typedef PtrToLNode List;
链式存储
用链建立各元素的逻辑关系,逻辑相邻元素不要求物理相邻(eg. 链表)。其基本结构与要素可以如下定义:
typedef struct LNode *PtrToLNode;
typedef struct LNode {
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;
线性表的基本操作
顺序存储
初始化
List Makelist() {
List list;
list = (List)malloc(sizeof(struct LNode));
list->Last = -1; /* 表示没有元素 */
return list;
}
求表长
需要考虑空表的情况,而空表的last等于-1,符合。
int Length(List list) { return list->Last + 1; }
查找:根据位序返回元素值
需要考虑K的合法性,包括太大和太小,对于空表位序一定不合法,所以不用特地考虑空表。
#define ERROR -1 /* 取一个正常情况下不可能取到的Data值 */
ElementType FindKth(List list, int K) { /* K表示位序,从1开始 */
if (K - 1 > list->Last || K < 1) {
printf("the %dth element do not exist\n", K);
return ERROR;
} else {
return list->Data[K - 1];
}
}
查找:根据元素值返回第一次出现的位置
#define ERROR -1
Position Find(List list, ElementType X) {
Position i;
for (i = 0; i <= list->Last && list->Data[i] != X; i++) {
;
}
if (i > list->Last) {
printf("the element to find do not exist\n");
return ERROR;
} else {
return i;
}
}
插入
需要考虑表是否已满(线性存储特有),K的合法性(包括太大和太小);从后向前移位。
bool Insert(List list, ElementType X, int K) {
Position i;
if (list->Last == MAXSIZE - 1) {
printf("list is full\n");
return false;
}
if (K < 1 || K > list->Last + 2) {
printf("illegal position\n");
return false;
}
for (i = list->Last; i >= K - 1; i--) {
list->Data[i + 1] = list->Data[i];
}
list->Data[K - 1] = X;
list->Last++;
return true;
}
删除
需要检查是否为空表,以及K的合法性。
bool Delete(List list, int K) {
Position i;
if (K < 1 || K > list->Last + 1) { /* 同时包含了空表和位序合法性的检查 */
printf("the %dth element do not exist\n", K);
return false;
}
for (i = K - 1; i < list->Last; i++) {
/* 如果只有一个元素,则不做循环,直接Last--,恰好也符合 */
list->Data[i] = list->Data[i + 1];
}
list->Last--;
return true;
}
链式存储
初始化
包含头结点,下述所有操作涉及的链表都包含头结点。
List Makelist() { /* 此处及以下都包含头结点 */
List list;
list = (List)malloc(sizeof(struct LNode));
list->Next = NULL;
return list;
}
求表长
需要考虑空表。
int Length(List list) {
Position p;
int cnt = 0;
for (p = list; p->Next != NULL; p = p->Next) {
cnt++;
}
return cnt;
}
查找:根据位序返回元素值
需要检验K的合法性以及是否为空表;此处往后检索的过程涉及两个条件,需要注意这两个条件恰好同时满足的情况是否为合法情况。
#define ERROR -1 /* 取一个正常情况下不可能取到的Data值 */
ElementType FindKth(List list, int K) {
Position p;
int cnt = 1;
for (p = list->Next; p && cnt < K; cnt++) {
p = p->Next;
}
if (cnt == K && p) { /* 检验K的合法性以及是否已经扫描到NULL */
return p->Data;
} else {
printf("the %dth element do not exist\n", K);
return ERROR;
}
}
查找:根据元素值返回第一次出现的位置
#define ERROR NULL
Position Find(List list, ElementType X) {
Position p;
for (p = list->Next; p && p->Data != X; p = p->Next) {
;
}
if (p) {
return p;
} else {
printf("the element to find do not exist\n");
return ERROR;
}
}
插入
需要找到第K的前一个,并考虑K的合法性,pre可以是头结点到最后一个非空节点,注意两个筛选条件同时成立的情况。(套用了FindKth)
bool Insert(List list, ElementType X, int K) {
Position pre, tmp;
int cnt = 0;
for (pre = list; pre && cnt < K - 1; cnt++) {
pre = pre->Next;
}
if (cnt == K - 1 && pre) {
tmp = (Position)malloc(sizeof(struct LNode));
tmp->Data = X;
tmp->Next = pre->Next;
pre->Next = tmp;
return true;
} else {
printf("illegal position\n");
return false;
}
}
删除
依然是套用FindKth,考虑K的合法性和两个循环条件同时满足,与插入不同的是其中pre->Next要求非空(这样也顺便检查了空表);最后注意释放删除的节点。
bool Delete(List list, int K) {
Position pre, tmp;
int cnt = 0;
for (pre = list; pre->Next && cnt < K - 1; cnt++) {
pre = pre->Next;
}
if (cnt == K - 1 && pre->Next) {
tmp = pre->Next;
pre->Next = tmp->Next;
free(tmp);
return true;
} else {
printf("the %dth element do not exist\n", K);
return false;
}
}
广义表与多重链表
广义表
广义表是线性表的推广,是由n个元素组成的有序序列,这些元素可以是单元素或另一个广义表。一般采取链式存储,用共同体(union)实现两种元素域的复用。
广义表数据结构可如下定义:
typedef enum { Data, Sublist } GNodeTag;
// enum中的名字与enum所属的同一级括号中的名字不能重复,
// 如果提前typedef就不用担心这个问题
typedef struct GNode *PtrToGNode;
typedef PtrToGNode GList;
struct GNode {
GNodeTag Tag;
union {
ElementType Data;
GList Sublist;
} URegion;
PtrToGNode Next;
};
多重链表
存在节点属于多个链的链表叫多重链表(eg. 如果广义表中某个节点指向了另一个链表,则可以构成一个多重链表)。
多重链表的节点有多个指针域,而节点有多个指针域的链表不一定是多重链表(eg. 双向链表)。
多重链表应用实例——稀疏矩阵数据结构定义(自编,与书中不同):
typedef enum { Head, Term, HHead } MNodeTag; /* HHead指头结点链表的头结点 */
struct TermNode {
int Row, Col;
ElementType Value;
};
struct HHeadNode {
int RowNumber, ColNumber;
int ElementNumber;
};
typedef struct MNode *PtrToMNode;
struct MNode {
MNodeTag Tag;
PtrToMNode Down, Right;
union {
PtrToMNode Next; /* 对应Head类型 */
struct TermNode Term;
struct HHeadNode HHead;
} URegion;
};
typedef PtrToMNode Matrix; /* 稀疏矩阵类型定义 */
Matrix heads[MAXSIZE]; /* heads是指向各个头结点的指针数组,用于快速指向 */
堆栈
堆栈的定义
堆栈是一种有约束的线性表,插入(压入,Push)和删除(弹出,Pop)操作都只能作用于端点位置(栈顶,Top),被称为后入先出(LIFO)表。
堆栈的实现(类型定义)
顺序存储
与前文线性表的类型定义不同,这里节点的MaxSize可以改变。
顺序存储中还可以实现双堆栈,即从两边向中间长,包含两个Top(栈空时分别指向-1和Maxsize,栈满时Top2 - Top1 == 1),进行操作时需要加一个Tag来区分操作对象。
以下仅展示单堆栈。
typedef int Position;
typedef struct SNode* PtrToSNode;
struct SNode {
ElementType* Data; /* 指向存放元素的数组 */
Position Top; /* -1为空,Maxsize-1为满 */
int Maxsize;
};
typedef PtrToSNode Stack;
链式存储
typedef struct SNode* PtrToSNode;
struct SNode {
ElementType Data;
PtrToSNode Next;
};
typedef PtrToSNode Stack;
堆栈的基本操作
顺序存储
初始化
Stack CreatStack(int maxsize) {
Stack stack = (Stack)malloc(sizeof(SNode));
stack->Data = (ElementType*)malloc(maxsize * sizeof(ElementType));
stack->Top = -1;
stack->Maxsize = maxsize;
return stack;
}
检验栈满
bool IsFull(Stack stack) { return stack->Top == stack->Maxsize - 1; }
检验栈空
bool IsEmpty(Stack stack) { return stack->Top == -1; }
入栈(Push)
bool Push(Stack stack, ElementType element) {
if (IsFull(stack)) {
printf("Stack is full.");
return false;
}
stack->Data[++stack->Top] = element;
return true;
}
出栈(Pop)
#define ERROR -999 /* 异常值 */
ElementType Pop(Stack stack){
if(IsEmpty(stack)){
printf("Stack is empty.");
return ERROR;
}
return stack->Data[stack->Top--];
}
链式存储
链式存储中不存在栈满的情况。
初始化
Stack CreateStack() {
Stack stack = (Stack)malloc(sizeof(SNode));
stack->Next = NULL;
return stack; /* 头结点 */
}
检验栈空
bool IsEmpty(Stack stack) { return stack->Next == NULL; }
入栈(Push)
不需要检查栈满。
bool Push(Stack stack, ElementType element) {
PtrToSNode tmpSNode = (PtrToSNode)malloc(sizeof(SNode));
tmpSNode->Data = element;
tmpSNode->Next = stack->Next;
stack->Next = tmpSNode;
return true;
}
出栈(Pop)
#define ERROR -999 /* 异常值 */
ElementType Pop(Stack stack) {
if (IsEmpty(stack)) {
printf("Stack is empty");
return ERROR;
}
PtrToSNode tmpSNode = stack->Next;
ElementType tmpData = tmpSNode->Data;
stack->Next = tmpSNode->Next;
free(tmpSNode);
return tmpData;
}
堆栈应用:中缀表达式转化为后缀表达式
- 从左到右扫描,遇到数字直接输出,遇到符号压入堆栈。
- 遇到下一个符号时,与栈顶符号比较,如果栈顶符号优先级更大或相等,依次弹出所有符合条件的栈顶符号,直到遇到不能弹出的为止;如果栈顶符号优先级更小,则不弹出。
- 将2中的新符号压栈,继续扫描,重复2、3两步。
- 如果遇到左括号,直接压栈,扫描下一个,左括号只有在遇到右括号时才会弹出(不计入最终的结果字符串)。
- 如果遇到右括号,依次弹出所有符号,直至左括号。
队列
队列的定义
队列是一种有约束的线性表,只能在一端插入(Rear),在另一端删除(Front),被称为先进先出(FIFO)表。
队列的实现(类型定义)
顺序存储
为了避免“假溢出”,可以采用循环队列。
从0开始存储,少用一个元素空间(让Front和Rear的差值能区别队列满和队列空),Front为空,Rear有元素,两者重合表示队列空,(Rear + 1) % MaxSize == Front表示队列满。
通过Rear % MaxSize即可从末端折返到起始单元,队列长度可表示为(Rear - Front + MaxSize) % MaxSize。
typedef struct QNode* PtrToQNode;
struct QNode {
ElementType* Data;
int Front, Rear;
int MaxSize;
};
typedef PtrToQNode Queue;
链式存储
链式存储中Front只能在链表头,因为链表尾不适合删除。
以下示例均带头结点。
typedef struct ElemNode* PtrToElemNode;
struct ElemNode {
ElementType Data;
PtrToElemNode Next;
};
typedef PtrToElemNode Position;
typedef struct QNode* PtrToQNode;
struct QNode {
Position Front, Rear;
int MaxSize;
};
typedef PtrToQNode Queue;
队列的基本操作
顺序存储
初始化
Queue CreatQueue(int maxsize) {
Queue queue = (Queue)malloc(sizeof(QNode));
queue->Data = (ElementType*)malloc(maxsize * sizeof(ElementType));
queue->Front = queue->Rear = 0;
queue->MaxSize = maxsize;
return queue;
}
判断队列满
bool IsFull(Queue queue) {
return (queue->Rear + 1) % queue->MaxSize == queue->Front;
}
判断队列空
bool IsEmpty(Queue queue) { return queue->Rear == queue->Front; }
插入
bool AddQ(Queue queue, ElementType element) {
if (IsFull(queue)) {
printf("queue is full");
return false;
}
queue->Rear = (queue->Rear + 1) % queue->MaxSize;
queue->Data[queue->Rear] = element;
return true;
}
删除并返回删除的元素
#define ERROR -999 /* 异常值 */
ElementType DeleteQ(Queue queue) {
if (IsEmpty(queue)) {
printf("queue is empty");
return ERROR;
}
queue->Front = (queue->Front + 1) % queue->MaxSize;
return queue->Data[queue->Front];
}
链式存储
均带头结点,和书上不一样,部分自编。
初始化
Queue CreatQueue(int maxsize) {
Queue queue = (Queue)malloc(sizeof(QNode));
queue->Front = queue->Rear = (PtrToElemNode)malloc(sizeof(ElemNode));
queue->Front->Next = NULL;
queue->MaxSize = maxsize;
}
判断队列空
bool IsEmpty(Queue queue) { return queue->Rear == queue->Front; }
插入
不需要判断队列满。
bool AddQ(Queue queue, ElementType element) {
PtrToElemNode tmp = (PtrToElemNode)malloc(sizeof(ElemNode));
tmp->Data = element;
tmp->Next = NULL;
queue->Rear->Next = tmp;
queue->Rear = tmp;
}
删除并返回删除元素
注意只有一个元素的情况,需要调整Rear的位置。
#define ERROR -999 /* 异常值 */
ElementType DeleteQ(Queue queue) {
if (IsEmpty(queue)) {
printf("queue is empty\n");
return ERROR;
}
Position tmpQNode = queue->Front->Next;
ElementType tmpData = tmpQNode->Data;
if (queue->Front->Next == queue->Rear) { /* 只有一个元素 */
queue->Rear = queue->Front;
}
queue->Front->Next = tmpQNode->Next;
free(tmpQNode);
return tmpData;
}