数据结构-2 线性结构

1,449 阅读9分钟

线性表

线性表的定义

线性表是由同一类型数据元素构成的有序序列,这种逻辑结构就是线性结构

线性表中元素个数称为线性表长度,长度为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;
};

image.png

image.png

多重链表

存在节点属于多个链的链表叫多重链表(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是指向各个头结点的指针数组,用于快速指向 */

image.png

image.png

堆栈

堆栈的定义

堆栈是一种有约束的线性表,插入(压入,Push)和删除(弹出,Pop)操作都只能作用于端点位置(栈顶,Top),被称为后入先出(LIFO)表。

堆栈的实现(类型定义)

顺序存储

与前文线性表的类型定义不同,这里节点的MaxSize可以改变。

顺序存储中还可以实现双堆栈,即从两边向中间长,包含两个Top(栈空时分别指向-1Maxsize,栈满时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;
}

堆栈应用:中缀表达式转化为后缀表达式

  1. 从左到右扫描,遇到数字直接输出,遇到符号压入堆栈。
  2. 遇到下一个符号时,与栈顶符号比较,如果栈顶符号优先级更大或相等,依次弹出所有符合条件的栈顶符号,直到遇到不能弹出的为止;如果栈顶符号优先级更小,则不弹出。
  3. 将2中的新符号压栈,继续扫描,重复2、3两步。
  4. 如果遇到左括号,直接压栈,扫描下一个,左括号只有在遇到右括号时才会弹出(不计入最终的结果字符串)。
  5. 如果遇到右括号,依次弹出所有符号,直至左括号。

队列

队列的定义

队列是一种有约束的线性表,只能在一端插入Rear),在另一端删除Front),被称为先进先出(FIFO)表。

队列的实现(类型定义)

顺序存储

为了避免“假溢出”,可以采用循环队列

0开始存储,少用一个元素空间(让FrontRear的差值能区别队列满和队列空),Front为空,Rear有元素,两者重合表示队列空,(Rear + 1) % MaxSize == Front表示队列满。

通过Rear % MaxSize即可从末端折返到起始单元,队列长度可表示为(Rear - Front + MaxSize) % MaxSize

dbdf8b8a9138da71d958adaa18bf6da.jpg

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;
}