数据结构——栈和队列

线性表

线性表的顺序存储

顺序表的存储结构描述如下:

#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,等于,(退栈不输出)
  • ’ * '退栈输出
  • ’ - '退栈输出
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值