线性表
线性表的基本概念
线性表的定义:线性表是一个具有相同特性的数据元素的有限序列。
相同特性:所有元素属于同一数据类型
有限:数据元素个数是有限的
序列:数据元素由逻辑序号(逻辑位序)唯一确定,一个线性表中可以有相同值的元素
线性表的基本运算
1. 初始化线性表:构造一个空的线性表
2. 销毁线性表
3. 判断线性表是否为空
4. 求线性表的长度
5. 输出线性表
6. 求线性表中指定位置的数据元素
7. 定位查找特定值的数据元素
8. 插入
9. 删除
线性表的顺序存储结构
按逻辑顺序依次存储到存储器中一片连续的存储空间中。
顺序表类型定义
顺序表的运算实现
#include<stdio.h>
#include<stdlib.h>#define ElemType char
#define MaxSize 100typedef struct {ElemType data[MaxSize];int length;
} SqList;//初始化顺序表
void InitList(SqList*& L) {L = (SqList*)malloc(sizeof(SqList));L->length = 0;
}//整体创建顺序表
void CreateList(SqList*& L, ElemType a[], int n) {L = (SqList*)malloc(sizeof(SqList));for (int i = 0; i < n; i++) {L->data[i] = a[i];}L->length = n;
}//销毁顺序表
void DestroyList(SqList*& L) {free(L);
}//判断是否为空表
bool ListEmpty(SqList*& L) {return L->length == 0;
}//求顺序表长度,直接返回length值即可
int ListLength(SqList*& L) {return L->length;
}//输出顺序表
void DispList(SqList*& L) {if (ListEmpty(L)) return; //这样不会多打印一个换行符for (int i = 0; i < L->length; i++) {printf("%c ", L->data[i]);}putchar('\n');
}//求第i个元素的值(i从1开始),放到e里面
bool GetElem(SqList*& L, int i, ElemType& e) {if (i < 1 || i > L->length) return false;e = L->data[i - 1];return true;
}//查找第一个值域与e相等的元素的逻辑位序(从1开始),找不到就返回0
int LocateElem(SqList*& L, ElemType e) {int i = 0;while (i < L->length && L->data[i] != e) {++i;}if (i == L->length) return 0;else return i + 1;// 逻辑位序和物理位序相差1
}// 在第i个位置(逻辑位序)插入新元素
bool InsertList(SqList*& L, int i, ElemType e) {// 检查一下输入的合法性if (i < 1 || i > L->length) return false;--i;// 把逻辑位序转化成物理位序,对于coder来说好操作;此后,i即物理位序// 把第i...length-1个元素都往后挪一位,把第i个位置腾出来for (int j = L->length - 1; j >= i; --j) {L->data[j + 1] = L->data[j];}L->data[i] = e;L->length += 1;// 注意顺序表的长度是我们需要去维护的!return true;
}// 删除第i个位置的数据元素,返回给e
bool DeleteList(SqList*& L, int i, ElemType& e) {// 检查输入合法性if (i < 1 || i > L->length) return false;--i;e = L->data[i];for (int j = i + 1; j < L->length; j++) {L->data[j] = L->data[j - 1];}L->length += 1; // 差点又忘记维护了!呜呜呜return true;
}
顺序表算法设计(进阶)
1. 已知长度为n的线性表A采用顺序存储结构。设计一个时间复杂度为On、空间复杂度为O1的算法,该算法删除线性表中所有值为x的数据元素。
算法1:空间复用,还算好理解
// 算法1,空间复用,直接覆盖
void DelAl1(SqList*& L, ElemType x) {int k = 0;for (int i = 0; i < L->length; i++) {if (L->data[i] != x) {L->data[k] = L->data[i];++k;}}L->length = k;
}
算法2:稍微有点抽象了
这个题目的难点在于时间复杂度的要求,On也就是说我们只能遍历一遍,在这唯一的一次遍历中完成所有移位操作。但是我们一开始肯定会想,当我第一次遍历,面对一个元素的时候,我怎么知道要把这个元素前移几位呢?
事实上这是可知的!你仔细想想,如果这个元素前面有x个需要移除的元素,那你是不是把这个元素往前移x位即可?
注意,k计数的是“需要被移除”的元素个数!所以最后顺序表的长度为原长-k
// 算法2,有点抽象
void DelAl2(SqList*& L, ElemType x) {int k = 0;for (int i = 0; i < L->length; i++) {if (L->data[i] == x) ++k;else {L->data[i - k] = L->data[i];}}L->length -= k;
}
2. 设顺序表L有10个整数。设计一个算法,以第一个元素为分界线,将所有小于等于它的元素移到该元素的前面,将所有大于它的元素移到该元素的后面。
void MoveAl1(SqList*& L) {ElemType pivot = L->data[0];int i = 0, j = L->data[L->length - 1];while (i < j) {//i < j这个条件必须一直约束着while (i < j && L->data[i] <= pivot) {//小于等于的话,那你就一边玩去,没你事儿~++i;}while (i < j && L->data[i] > pivot) {++j;}if (i < j) {ElemType temp = L->data[i];L->data[i] = L->data[j];L->data[j] = temp;}}ElemType temp = L->data[0];L->data[0] = L->data[i];L->data[i] = temp;
}
线性表的链式存储结构
单链表:每个物理节点增加一个指向后继节点的指针域
双链表:每个物理节点增加一个指向后继节点的指针域和一个指向前驱节点的指针域
这里提出一个存储密度的概念:
单链表中节点类型LinkList的定义
typedef struct LNode{ElemType data;struct LNode* next;
} LinkList;
单链表建表——头插法、尾插法
头插法:
1. 从一个空表开始,创建一个头节点dummyHead。
2. 依次读取字符数组中的元素,生成新节点。
3. 将新节点插入到当前链表的表头上,直到结束为止。
(注意:链表的节点顺序与逻辑次序相反)
//头插法建表
void CreateListByHead(LinkList*& L, ElemType a[], int n) {L = (LinkList*)malloc(sizeof(LinkList));L->next = NULL;for (int i = 0; i < n; i++) {LNode* newNode = (LNode*)malloc(sizeof(LNode));newNode->data = a[i];newNode->next = L->next;L->next = newNode;}
}
尾插法:
1. 从一个空表开始,创建一个头节点dummyHead。
2. 依次读取字符数组a中的元素,生成新节点。
3. 将新节点插入到当前链表的表尾上,直到结束为止。
(链表的节点顺序与逻辑次序相同)
单链表的基本运算
单链表的算法设计
以查找为基础
1. 设计一个算法,删除一个单链表L中元素值最大的节点(假设最大值节点是唯一的)
// 删除最大节点
void DelMaxNode(LinkList*& L) {if (ListEmpty(L)) return;LNode* pre = L, * maxPre = L;while (pre->next != NULL) {if (pre->next->data > maxPre->next->data) {maxPre = pre;}pre = pre->next;}maxPre->next = maxPre->next->next; //不会出现空指针异常!因为maxPre后面一定是指着一个节点的!
}
2. 有一个带头节点的单链表L(至少有一个数据节点),设计一个算法使其元素递增有序排列。
我不懂
// 排序算法,我不懂
void SortList(LinkList*& L) {LNode* p = L->next, * pre;LNode* r = p->next;// r作为p放入后继,防止断链p->next = NULL; // 构造只有一个数据节点的有序表p = r;while (p != NULL) {r = p->next;// r防止断链,又接回去了?!pre = L;while (pre->next != NULL && pre->next->data < p->data) {pre = pre->next;}p->next = pre->next;pre->next = p;p = r;}
}
把链表转化成数组问题貌似可以,毕竟对数组进行排序的算法可多了。
以建表为基础
1. 假设有一个带头节点的单链表L,设计一个算法将所有节点逆置。
用头插法不就是直接形成一个逆置链表了嘛!
// 逆置链表
void ReverseList(LinkList*& L) {LinkList* p = L->next, * q;L->next = NULL;while (p != NULL) {q = p->next;p->next = L->next;L->next = p;// 让p成为逆置链表的新的头节点p = q;}
}
栈
栈的基本概念
栈的定义:只能在一端进行插入或删除操作的线性表
允许进行插入、删除操作的一端——栈顶
栈的顺序存储结构
顺序栈的基本运算实现
#include<stdio.h>
#include<stdlib.h>#define ElemType int
#define MaxSize 10typedef struct {ElemType data[MaxSize];int top;
} SqStack;// 初始化栈,创建一个空栈
void InitStack(SqStack*& S) {S = (SqStack*)malloc(sizeof(SqStack));S->top = -1;
}// 销毁栈
void DstroyStack(SqStack*& S) {free(S);
}// 判断栈是否为空,若栈为空则返回true
bool StackEmpty(SqStack*& S) {return (S->top == -1)
}// 进栈
bool PushStack(SqStack*& S, ElemType e) {// 顺序栈可能出现栈满的情况if (S->top == MaxSize - 1) {return false;}S->top++;S->data[S->top] = e;return true;
}// 出栈
bool PopStack(SqStack*& S, ElemType& e) {if (StackEmpty(S)) {return false;}e = S->data[S->top];S->top--;return true;
}// 取栈顶元素
bool GetTop(SqStack*& S, ElemType& e) {if (StackEmpty(S)) {return false;}e = S->data[S->top];return true;
}
顺序栈的算法设计
栈这种先进后出的思想可以用来解决“回文字符串”问题
1. 设计一个算法利用顺序栈判断一个字符串是否是对称串
// 判断回文字符串
bool SymmetryString(ElemType str[]) {SqStack* st;ElemType temp;InitStack(st);for (int i = 0; str[i]; i++) {PushStack(st, str[i]);}for (int i = 0; str[i]; i++) {PopStack(st, temp);if (temp != str[i]) {return false;}}DestroyStack(st);return true;
}
栈的链式存储结构
头插法,链表的真实头节点即栈顶
链式栈的基本运算实现
#include<stdio.h>
#include<stdlib.h>#define ElemType chartypedef struct LinkNode {ElemType data;LinkNode* next;
} LkStack;// 初始化栈
void InitStack(LkStack*& S) {S = (LkStack*)malloc(sizeof(LkStack));S->next == NULL;
}// 销毁栈
void DestroyStack(LkStack*& S) {LinkNode* p = S->next, *q;while (p != NULL) {q = p->next;free(p);p = q;}free(S);
}// 判断栈是否为空
bool StackEmpty(LkStack*& S) {return (S->next == NULL);
}// 进栈
void PushStack(LkStack*& S, ElemType e) {LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));newNode->data = e;newNode->next = S->next;S->next = newNode;
}// 出栈,出栈可能失败因此返回值设置为bool
bool PopStack(LkStack*& S, ElemType &e) {if (StackEmpty(S)) {return false;}e = S->next->data;LinkNode* temp = S->next;S->next = temp->next;free(temp);return true;
}// 取栈顶元素
bool GetTop(LkStack*& S, ElemType &e) {if (StackEmpty(S)) {return false;}e = S->next->data;return true;
}// 遍历栈
bool DispStack(LkStack*& S) {if (StackEmpty(S)) {return false;}LinkNode* p = S->next;while (p != NULL) {printf("%c\n", p->data);p = p->next;}
}int main() {LkStack* st;InitStack(st);PushStack(st, 'a');DispStack(st);return 0;
}
链式栈的算法设计
1. 编写一个算法判断表达式的左右括号是否匹配(假设只含有左右圆括号)
// 判断表达式的左右括号是否匹配
bool IsMatch(char expr[]) {LkStack* st;ElemType e;InitStack(st);for (int i = 0; expr[i]; i++) {// 遇到左括号就把左括号入栈if (expr[i] == '(') {PushStack(st, expr[i]);}else if (expr[i] == ')') {if (GetTop(st, e)) {if (e == '(') {PopStack(st, e);}else {return false;}}}}// 如果遍历完整个字符串之后,栈刚好为空,则返回trueif (StackEmpty(st)) {DestroyStack(st);return true;}else {DestroyStack(st);return false;}
}
需要注意的点是:你对几个栈的基本函数必须熟悉啊!返回值是多少?参数列表如何设定?
比如你用到PopStack这个函数,你是把Pop出去的那个元素的值放到e里面,你是这么些PopStack(st,e),然后你再对e这个值及逆行判断。
卧槽要是你直接写if(PopStack(st,e) == ’)’)不就寄了。
队列
队列是一种运算受限的线性表。
插入是从队尾插入(很合理,我们排队也是从队尾开始排队呢)
队列的顺序存储结构-顺序队
顺序队的基本算法实现
#include<stdio.h>
#include<stdlib.h>#define ElemType char
#define MaxSize 10typedef struct {ElemType data[MaxSize];int front, rear; // 定义队头和队尾
} SqQueue;// 初始化队列
void InitQueue(SqQueue*& Q) {Q = (SqQueue*)malloc(sizeof(SqQueue));Q->front = Q->rear = -1;
}// 销毁队列
void DestroyQueue(SqQueue*& Q) {free(Q);
}// 判断队列是否为空
bool QueueEmpty(SqQueue*& Q) {return (Q->front == Q->rear);
}// 进队列
bool InQueue(SqQueue*& Q, ElemType e) {// 顺序队要先判断是否队满if (Q->rear == MaxSize - 1) {return false;}Q->rear++;Q->data[Q->rear] = e;return true;
}// 出队列
bool OutQueue(SqQueue*& Q, ElemType &e) {if (QueueEmpty(Q)) {return false;}Q->front++;e = Q->data[Q->front];return true;
}
环形顺序队
为什么rear需要+1,因为,你要进队啊!那你不得把指针往后挪一位。为什么要取余,因为是环形队列,这里有一个循环,尾接着头。
为什么front需要+1,因为front指向的是队头的前一个节点,你要把front+1之后才能确切指向队头。取余原因同理
#include<stdio.h>
#include<stdlib.h>#define ElemType char
#define MaxSize 10typedef struct {ElemType data[MaxSize];int front;int count; // 设置一个count保存元素个数
} CQueue;// 初始化队列
void InitQueue(CQueue*& CQ) {CQ = (CQueue*)malloc(sizeof(CQueue));CQ->front = CQ->count = 0;
}// 入队
bool InQueue(CQueue*& CQ, ElemType e) {if (CQ->count == MaxSize) {return false;}int rear = (CQ->front + CQ->count) % MaxSize;rear = (rear + 1) % MaxSize;// 加1是一定要加1的,环形队列是需要取余的。CQ->data[rear] = e;CQ ->count++;return true;
}// 出队
bool OutQueue(CQueue*& CQ, ElemType& e) {// 这里不需要判断为空函数了,count直接保存该队列的元素值,注意维护!if (CQ->count == 0) {return false;}CQ->front = (CQ->front + 1) % MaxSize;e = CQ->data[CQ->front];CQ->count--;return true;
}
队列的链式存储结构-链式队
#include<stdio.h>
#include<stdlib.h>#define ElemType chartypedef struct qNode {ElemType data;struct qNode* next;
} QNode;typedef struct {QNode* front; // 指向链式队头节点QNode* rear; // 指向链式队尾节点
} LkQueue;// 初始化
void InitQueue(LkQueue*& LQ) {LQ = (LkQueue*)malloc(sizeof(LkQueue));LQ->front = LQ->rear = NULL;
}// 销毁
void DestroyQueue(LkQueue*& LQ) {QNode* p = LQ->front, *q;while (p != NULL) {q = p->next;free(p);p = q;}free(LQ);
}// 判断队列是否为空
bool QueueEmpty(LkQueue*& LQ) {return (LQ->front == NULL);
}// 进队
void InQueue(LkQueue*& LQ, ElemType e) {QNode* newNode = (QNode*)malloc(sizeof(QNode));newNode->data = e;newNode->next = NULL;if (QueueEmpty(LQ)) {LQ->front = LQ->rear = newNode;}else {LQ->rear->next = newNode;LQ->rear = newNode;}
}bool OutQueue(LkQueue*& LQ, ElemType &e) {if (QueueEmpty(LQ)) {return false;}QNode* temp = LQ->front;LQ->front = temp->next;e = temp->data;// 如果只有一个节点,那要对rear也进行修改if (temp->next == NULL) {free(temp);LQ->front = LQ->rear = NULL;}else {LQ->front = temp->next;free(temp);}return true;
}// 遍历
bool DispQueue(LkQueue*& LQ) {if (QueueEmpty(LQ)) {return false;}QNode* p = LQ->front;while (p != NULL) {printf("%c ", p->data);p = p->next;}
}int main() {LkQueue* LQ;InitQueue(LQ);InQueue(LQ, 'a');InQueue(LQ, 'b');InQueue(LQ, 'c');ElemType e;OutQueue(LQ, e);DispQueue(LQ);return 0;
}
环形链式队
栈和队列的应用
1. 简单表达式求值
postfix后缀表达式计算
好的,那么问题来了,我们如何将infix转换为postfix呢?
没括号情况
有括号情况