数据结构复习(三)线性结构(2)

弹夹——栈

线性结构栈,十分神似弹夹。众所周知,弹夹上弹是从入口处压入,而使用的时候也是从顶部取出。所以先压入的子弹会被后使用。栈就是如此,数据进入的时候从栈顶进入;取出的时候也是从栈顶取出。

栈是顺序表的变种,她只能从顶部插入或删除。让我们回忆一下顺序表的定义方式。顺序表的结构体内有两个成员,一个是用于存放数据的数组、另一个是指示结尾的Last。让我们把顺序表竖起来,就是一个栈了。我们在栈中放入一个新的成员变量,用于指示栈的最大容量。所以栈的定义就跃然纸上了。

typedef int ElementType;
typedef int Position;
typedef struct SNode * PtrToSNode;
struct SNode{
	ElementType * Data;
	// 定义了一个数组
	Position Top;
	// 栈顶指针
	int MaxSize;
	// 栈的最大容量 
};
typedef PtrToSNode Stack;

顺便,我将创建栈空间的代码附上。其实跟创建顺序表的非常相似。

Stack CreateStack(int MaxSize){
	Stack s = (Stack)malloc(sizeof(struct SNode));
	s->Data=(ElementType *)malloc(MaxSize*sizeof(ElementType));
	s->MaxSize=MaxSize;
	s->Top=-1;
	return s;
}

根据刚刚的定义,栈最主要的两个操作就是出栈入栈

入栈的时候,我们只要先判断栈空间有没有满,然后将Top+1并且把元素放入数组的Top下标的位置即可。接下来我们瞅瞅入栈的函数Push。

bool Push(Stack S, ElementType x){
	if(S->Top==S->MaxSize-1){
		printf("栈空间已满");
	}else{
		S->Top+=1;
		S->Data[S->Top]=x;
		// 也可以写成S->Data[++(S->Top)]=x; 
		return true;
	}
}

出栈跟入栈差不多,只需要先判断栈是否为空,然后获取数组Top下标的元素,最后Top--即可。

ElementType Pop(Stack S){
	if(S->Top==-1){
		printf("栈为空");
		return -1;
	}else{
		return S->Data[(S->Top)--];
	}
}

这样一个简单的栈就完美实现了。但是用数组实现的话,会导致空间有一丢丢的浪费,所以有人就想出了个法子,一个数组塞两个栈。

聪明的大家一定已经想到了,我们只需要搞两个栈顶指针,一个指向数组下标为-1的地方,另一个指向Maxsize即可,这样从头开始是一个栈,从脚开始也是一个栈。让我们来看看定义。

typedef int ElementType;
typedef int Position;
typedef struct SNode * PtrToSNode;
struct SNode{
	ElementType * Data;
	// 定义了一个数组
	Position Top1;
    Position Top2;
	int MaxSize;
	// 栈的最大容量 
};
typedef PtrToSNode Stack;

就是如此简单,只是将前文的定义加上了一个新的指针而已。但是在判空和判断栈满方面略有不同。判空相对简单,所以我只展示一下判断栈是否满了。

bool IsFull(Stack S){
    return(S->Top2-S->Top1==1);
}

肥肠简单,对吧。

那么肯定又有聪明的小可爱要问了,为啥要用数组实现呢,用链表实现岂不是不会导致空间的浪费?

太对了,所以接下来我们一起瞅瞅用链表实现的栈。

温故而知新,再看一次链表节点的定义:

typedef int ElementType;
typedef struct LNode * PtrToLNode;
struct LNode{
	ElementType Data;
	PtrToLNode Next;
};
typedef PtrToLNode Stack;

接下来我们可以定义一个头节点,为了方便操作,我们将头节点作为栈的顶部。

Stack CreateStack(){
    Stack s;
    s=(Stack)malloc(sizeof(struct LNode));
    s->Next=NULL;
    return s;
}

这个栈很明显是无上限的,因为入栈的时候我们只要加一个链表节点进去即可。所以我们来研究一下如何判空。显然当头节点的指针域为空时,栈即为空。

bool IsEmpty(Stack s){
    return(s->Next==NULL);
}

入栈操作就比较耐人寻味了。跟链表的头插法很相似,我们只需要将新分配一个节点空间,然后填充数据域,再填充指针域为头节点的next,最后将头节点的next指向这个新节点即可。

ElementType Pop(Stack s){
	PtrToSNode FirstCell;
	// 保存当前的第一个节点 
	ElementType TopElem;
	// 保存要返回的元素 
	if(IsEmtpy(s)){
		printf("空栈");
		return -1;
	}else{
		FirstCell = s->Next;
		TopElem = FirstCell->Data;
		s->Next=FirstCell->Next;
		free(FirstCell);
		return TopElem;
	}
}

排排列,吃果果——队列

队列,顾名思义就是一个队列(笑),如果说栈是先进的后出,那队列就是先进的先出。同样,队列也可以用老朋友数组来实现。但是因为进入队列要从尾部进去,而出队要从头部出,所以我们需要两个指针,一个指向头一个指向尾。所以咱们就能获得一个这样的结构:

在插入元素的时候,我们只要将rear+1,然后插入到rear+1的位置即可。取出元素的时候我们只要将front+1即可,肥肠简单。但是这样会带来一个小问题。

随着不断取出和加入,当出现图示情况的时候,我们认为队列已经满了,但是事实上并没有。 对此,有大佬提出了一种解决方案——循环队列。循环队列就是把一个数组首尾相连,变成环状态,然后就可以避免上述问题啦。

(画图技术太差,所以不画了,大家可以自己想象一下)

所以我们来看一看循环队列的实现。我们肯定需要一个数组,两个指针,还有一个最大容量。

typedef int ElementType;
typedef int Position;
typedef struct QNode * PtrToQNode;
struct QNode{
	ElementType * Data;
	Position Front, Rear;
	int MaxSize;
};
typedef PtrToQNode Queue;

创建队列的函数跟创建栈的函数很像:

Queue CreateQueue(int MaxSize){
	Queue q;
	q=(Queue)malloc(sizeof(struct QNode));
	q->Data=(ElementType *)malloc(MaxSize*sizeof(ElementType));
	q->Front=q->Rear=0;
	q->MaxSize=MaxSize;
	return q; 
}

对于一个长度为10的循环队列,如何判断是否已经满了或空了呢?

判空相对简单,我们只需要判断front和rear是否相等即可。

反观判满,就可能出现两种情况。第一种,假如front=0,rear=9,即为队列满了。我们可以归纳一个条件出来就是rear-front=maxsize-1.但是还有一种情况,假如front=2,rear=1。由于front是头结点不存储数据,所以也应当视为队列满了,但是这样就无法使用刚刚的条件了。我们来提取一下当前这种情况,会得到rear+1=front。我们发现,第一个式子经过变形可以得到(rear+1)-front=maxsize,第二个式子可以变成(rear+1)-front=0。通过不懈努力,两个式子只相差一个maxsize了,接下来我们就需要用一个特殊的运算将maxsize和0在逻辑上划上等号。通过让rear+1%maxsize的方式,我们就能消解“圈数”带来的影响。因为rear+1与front之间的差值要么为maxsize,要么为0,所以对rear+1取maxsize余数的结果必然等于front。综上所述,最终判满的式子就是:

bool IsFull(Queue q){
	return((q->Rear+1)%q->MaxSize==q->Front);
}

接下来让我们补全加入队列和从队列中取出的过程即可。由于是循环队列,所以加入队列的时候不能光让rear++,万一超过了数组长度可就不好玩了。当达到末尾的时候,我们也可以用%maxsize的方式巧妙得让尾巴“兜”回来。

bool AddQ(Queue q, ElementType x){
	if(IsFull(q)){
		printf("队列已满");
		return false;
	}else{
		q->Rear=(q->Rear+1)%q->MaxSize;
		q->Data[q->Rear]=x;
		return true;
	}
}

同理,取出的时候我们也要让头“兜”回来。

ElementType DeleteQ(Queue q){
	if(q->Rear==q->Front){
		printf("队列已空");
		return -1;
	}else{
		q->Front=(q->Front+1)%q->MaxSize;
		return q->Data[q->Front];
	}
}

与栈相同,队列也可以用链式存储结构实现。但是由于链式存储结构只能从头遍历到尾,所以队列的头部必须是链表的头部,尾部也必须是链表的尾部。进入队列的时候我们需要将尾部的节点指向入队节点;出队的时候我们只需要free掉头部的节点即可。相关代码比较简单,故按下不表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值