线性表
线性表的顺序存储
顺序表的存储结构描述如下:
#define MaxSize 100
typedef int DataType;
typedef struct List
{
DataType num[MaxSize];
int length;
}SeqList;
顺序表的初始化:
void InitList(SeqList& L)
{
L.length = 0;
}
顺序表的元素插入
void ListInsert(SeqList& L, DataType x, int i) //将元素x插入在第i个位置处
{
if (i > L.length + 1 || i < 1)
{
printf("位置参数不合法!");
return;
}
if (L.length == MaxSize)
{
printf("顺序表已满无法插入!");
return;
}
//j - 1 >= i - 1, j >= i
for (int j = L.length; j >= i; j--)
L.num[j] = L.num[j - 1];
L.num[i - 1] = x;
L.length++;
}
根据值删除元素
int DeleteByData(SeqList& L, DataType x) //删除数据为x的元素并返回在顺序表的位置
{
int j = 0;
while (L.num[j++] != x && j <= L.length - 1);
if (j == L.length)
{
printf("表中没有元素%d\n", x);
return 0;
}
else
{
for (int k = j; k++; k < L.length)
L.num[k - 1] = L.num[k];
L.length--;
return j;
}
}
根据位置删除元素
DataType DeleteByLoc(SeqList& L, int i) //第i个元素在数组的下标为i - 1
{
if (i > L.length + 1 || i < 1)
{
printf("位置参数不合法!");
return 0;
}
if (L.length == 0)
{
printf("顺序表为空!");
return 0;
}
for (int j = i; j++; j < L.length)
L.num[j - 1] = L.num[j];
L.length--;
return L.num[i - 1];
}
顺序有序表的合并
void MergeList(SeqList La, SeqList Lb, SeqList Lc)
{
int i = 0, j = 0, k = 0;
while (i < La.length && j < Lb.length)
{
if (La.num[i] < Lb.num[j])
Lc.num[k++] = La.num[i++];
else Lc.num[k++] = Lb.num[j++];
}
while(i < La.length)
Lc.num[k++] = La.num[i++];
while(j < Lb.length)
Lc.num[k++] = Lb.num[j++];
Lc.length = La.length + Lb.length;
}
顺序表的链式存储
链表的结点定义:
typedef int DataType;
typedef struct Node
{
DataType val;
struct Node* next;
}LNode, *SLinkList;
链表的建立(头插法)
LNode* InitNode(DataType x)
{
LNode* cur = (LNode*)malloc(sizeof(LNode));
cur->val = x;
cur->next = NULL;
return cur;
}
void Create_HeadInsert(LNode* head, int i)
{
printf("请输入%d个DataType类型的数据用来创建链表\n", i);
int num;
for (int j = 0; j++; j < i)
{
scanf("%d", &num);
LNode* cur = InitNode(num);
LNode* aft = head->next;
head->next = cur;
cur->next = aft;
}
}
尾插法
void Create_TailInsert(LNode* head, int i)
{
printf("请输入%d个DataType类型的数据用来创建链表\n", i);
LNode* tail = head;
int num;
for (int j = 0; j++; j < i)
{
scanf("%d", &num);
LNode* cur = InitNode(num);
tail->next = cur;
cur->next = NULL;
tail = cur;
}
}
链式有序表的合并
void MergeSLinkList(SLinkList La, SLinkList Lb, SLinkList Lc)
{
LNode* a = La, *b = Lb, *c = Lc;
while (a != NULL && b != NULL)
{
if (a->val < b->val)
{
c->next = a;
a = a->next;
}
else
{
c->next = b;
b = b->next;
}
c = c->next;
}
if (a != NULL) c->next = a;
else c->next = b;
}
双向链表
双向链表的结点结构为:
typedef int DataType;
typedef struct node
{
DataType val;
struct node* prior;
struct node* next;
}DNode, *DLinkList;
插入元素
void DListInsert(DNode* head, DataType x, int i)
{
DNode* ptr = head;
int j;
for (j = 0; j++; j < i)
ptr = ptr->next;
if (j != i)
{
printf("位置参数输入错误!\n");
return;
}
DNode* cur = InitDNode(x);
cur->prior = ptr->prior;
ptr->prior = cur;
cur->prior->next = cur;
cur->next = ptr;
}
删除元素
DataType DListDelete(DNode* head, int i)
{
DNode* ptr = head;
int j;
for (j = 1; j++; j <= i)
ptr = ptr->next;
if (j != i)
{
printf("位置参数错误!\n");
return;
}
ptr->prior->next = ptr->next;
ptr->next->prior = ptr->prior;
DataType num = ptr->val;
free(ptr);
return num;
}
顺序表与链表的比较
1.时间性能比较
顺序表按位置查找的时间复杂度为O(1),插入和删除操作的时间复杂度为O(n).对于链表当确定插入和删除的位置时,无需移动元素只需修改指针,时间复杂度为O(1)。 而查找元素最坏的情况需要遍历整个链表,时间复杂度为O(n)。
2.空间性能比较
顺序表的存储空间是提前分配的。如果线性表的长度变化大,过小会造成空间浪费,过大会造成溢出。链表的存储密度低但不需要提前分配内存,只要内存允许链表可以无限长。
栈
将表中允许进行插入和删除的一端称为栈顶,另一端称为栈底,栈具有后进先出的特性。
顺序栈
顺序栈是利用一组连续的存储单元依次存放栈底到栈顶的元素。和顺序表类似,顺序栈的元素存储利用一个一维数组实现并附加一个指针top来指示当前栈顶的位置,通常用top = 0表示空栈。(当top = 0表示空栈时,栈顶指针指向栈顶元素的下一个元素;当top = -1表示空栈时,栈顶指针指向栈顶元素)
顺序栈的定义如下:
#define MaxSize 100
typedef int DataType;
typedef struct
{
DataType stack[MaxSize];
int top;
}SeqStack;
入栈和出栈:
void InitStack(SeqStack& s)
{
s.top = 0;
}
void StackPush(SeqStack& s, DataType x)
{
if (s.top == MaxSize)
{
printf("栈已满不能插入!\n");
return;
}
s.stack[s.top++] = x;
}
DataType StackPop(SeqStack& s)
{
if (s.top == 0)
{
printf("栈为空,无元素出栈!\n");
return 0;
}
return s.stack[--s.top];
}
链栈
链栈用单链表表示,链栈的结点结构与单链表的结点结构相同。
typedef struct snode
{
DataType val;
struct snode* next;
}LSNode, *LinkStack;
以链表的头结点作为栈顶,入栈和出栈操作为:
void InitStack(LinkStack& s) //链表初始化为空
{
s = NULL;
}
void LStackPush(LinkStack& s, DataType x)
{
LSNode* cur = InitNode(x);
cur->next = s;
s = cur;
}
DataType LStackPop(LinkStack& s)
{
if (s == NULL)
{
printf("栈为空,无元素可出栈!\n");
return 0;
}
LSNode* pre = s;
s = s->next;
DataType num = pre->val;
free(pre);
return num;
}
队列
队列允许插入的一端称为队尾,允许删除的一端称为队头。队列具有先进先出的特性,因此又称为先进先出的线性表。
顺序队列
顺序队列通常使用一维数组作为队列的顺序存储空间,另外再设两个指示器front和rear分别指示队头元素和队尾元素的位置。顺序队列的存储结构为:
typedef struct
{
DataType que[MaxSize];
int front;
int rear;
}SeqQueue;
初始化队列时,空队列的fornt = rear = 0, 当插入元素时,尾指针rear加1;当队头元素出队列时,头指针front加1.在非空队列中,头指针始终指向队头元素,尾指针始终指向队尾元素的下一个元素。随着入队,出队操作的进行会使整个队列向队尾的方向移动。当队尾指针移到最后不可再有新元素入栈,否则会导致溢出,而此时队列的实际内存空间并没有满,这种现象称为假溢出。
循环队列
解决假溢出的方法之一是将队列看成首尾相接的循环结构,称之为循环队列。头指针和尾指针的关系不变,只是当rear和front到达MaxSize-1后,再前进一个位置就自动到零。头尾相接的循环结构可以通过取模实现,但循环队列的队满和队空的条件相同。解决循环队列无法区分队满和队空的方法有以下三种:
1.少用一个元素空间,即队尾指针加一等于队头指针时认为队满。
队满条件为:(rear + 1) % MaxSize == front
队空条件为:rear == front
2.设置一个标志位tag初始化为0,每当入队操作成功时置tag = 1,出队操作成功时置tag = 0.
队满条件为:rear == front && tag == 1
队空条件为:rear == front && tag == 0
3.设置一个计数器count来记录队列中元素的个数,初始化为0。每当入队一个元素时count就加1,出队一个元素时就减1.
队空条件时:count == 0
队满条件是:count == MaxSize
下面用第三种方法实现循环队列的基本操作。
循环队列的存储类型定义:
typedef struct
{
DataType que[MaxSize];
int front;
int rear;
int count;
}SeqCQueue;
入队和出队:
void InitCQueue(SeqCQueue q)
{
q.front = q.rear = q.count = 0;
}
void CQueueAdd(SeqCQueue q, DataType x)
{
if (q.count == MaxSize)
{
printf("队列已满无法入队!\n");
return;
}
q.que[q.rear] = x;
q.rear = (q.rear + 1) % MaxSize;
q.count++;
}
DataType CQueueDel(SeqCQueue q)
{
if (q.count == 0)
{
printf("队列为空无法删除!\n");
return 0;
}
DataType num = q.que[q.front];
q.front = (q.front + 1) % MaxSize;
q.count--;
return num;
}
循环队列的所有操作的时间复杂度都为O(1).
链队列
链队列用单链表表示,并设置一个队头指针front和一个队尾指针rear,队头指针指向队列的队头结点,队尾指针指向队尾结点。
链队列的结点存储结构为:
typedef struct node
{
DataType data;
struct node* next;
}LQNode;
typedef struct
{
LQNode* front;
LQNode* rear;
}LQueue;
入队和出队:
void InitLQueue(LQueue& q)
{
q.front = NULL;
q.rear = NULL;
}
void LQueueAdd(LQueue& q, DataType x)
{
LQNode* cur = (LQNode*)malloc(sizeof(LQNode));
cur->data = x;
if (q.front == NULL) q.front = q.rear = cur;
else
{
cur->next = q.rear;
q.rear = cur;
}
}
DataType LQueueDel(LQueue& q)
{
if (q.front == NULL)
{
printf("队列已空无法删除!\n");
return 0;
}
LQNode* cur = q.front;
q.front = q.front->next;
DataType num = cur->data;
free(cur);
return num;
}
中缀表达式转化为后缀表达式
1.如果ch是操作数,则直接输出并继续读取下一个字符ch。
2.如果ch是运算符,则比较ch的优先级与当前栈顶元素的优先级:如果大于则入栈,如果小于则栈顶元素退栈并输出,如果等于则退栈但不输出。如果推出的是“( ”则接着读取下一个字符。
运算符优先级:(#的优先级为0)
以中缀表达式 8-(3+5)*(5-6/2)为例转换为后缀表达式(835+562/- * -)的过程:
- ‘ # ’入栈
- 输出8
- ’ - '入栈(-在栈外的优先级为3,#的优先级为0,-入栈)
- ’ ( '入栈((在栈外的优先级为6,-在栈内的优先级为2,大于,入栈)
- 输出3
- ’ + '入栈(+在栈外的优先级为2,(在栈内的优先级为1,大于,入栈)
- 输出5
- ’ + '退栈并输出 ( ‘)’ 在栈外的优先级为1,+在栈内的优先级为2,小于,+退栈并输出)
- ’ ( ‘退栈不输出 (’('在栈内的优先级为1, )在栈外的优先级为1,等于,(退栈但不输出,继续读下一个字符)
- ’ * '入栈 (-在栈内的优先级为2,*在栈外的优先级为4, 大于,入栈)
- ’ ( ‘入栈(’('在栈外的优先级为6,*在栈内的优先级为5,大于,入栈)
- 输出5
- ’ - '入栈(-在栈外的优先级为3, (在栈内的优先级为1,大于,入栈)
- 输出6
- ’ / '入栈(/在栈外的优先级为4,-在栈内的优先级为2,大于,入栈)
- 输出2
- ’ / ‘退栈并输出(’)'在栈外的优先级为1, /在栈内的优先级为5, 小于,/退栈并输出)
- ’ - ‘退栈并输出(’)'在栈外的优先级为1,-在栈内的优先级为2,小于,-退栈并输出)
- ’ ( ‘退栈不输出(’)'在栈外的优先级为1,(在栈内的优先级为1,等于,(退栈不输出)
- ’ * '退栈输出
- ’ - '退栈输出