这是两种受限操作的线性表。
1 栈
1.1 栈的概念
- 栈(stack):限制在一端进行插入和删除操作的线性表。LIFO(last in first out)线性表。
- 栈顶:进行插入和删除操作的一端。使用栈顶指针top指示栈顶元素。
- 栈底:固定端,又称表头。
- 空栈:当表中没有元素时称为空栈。
1.2 栈的顺序存储表示
顺序栈用一维数组存储栈,根据数组是否可以根据需求扩大分为:
- 静态顺序栈:实现简单,但不能根据需要增大栈的存储空间。
- 动态顺序栈:实现稍复杂,但可以根据需要增大栈的存储空间。
1.2.1 栈的动态顺序存储表示
1.2.1.1 实现要点:
bottom
为栈底指针,栈底固定不变;栈顶指针top
(指示当前栈顶位置)随着进栈和出栈操作而变化。- 栈空条件:
top=bottom
- 节点进栈:首先将数据元素保存到
top
所指向的位置,然后执行top+1
,其指向栈顶的下一个存储位置。 - 节点出栈:首先将
top
减一(此时指向栈顶元素的位置),然后将栈顶元素取出。
1.2.1.2 实现code
栈的类型定义
#define STACK_SIZE 100 // 初始大小
#define STACKCREMENT 10 // 存储空间增量大小
typedef int ElemenType;
typedef struct sqstack{
ElemType *bottom; // 栈不存在时为NULL
ElemType *bottom; // 栈顶指针
int stacksize; // 当前已分配空间
}SqStack;
栈的初始化
bool Init_Stack(void)
{
SqStack S;
S.bottom = (ElemType *)malloc(STACK_SIZE*sizeof(ElemType));
if(!S.bottom)
return false;
S.top = S.bottom; // 栈空
S.stacksize = STACK_SIZE;
return true;
}
元素进栈
bool push(SqStack S, ElemType e)
{
if(S.top-S.bottom >= S.stacksize-1) // 栈满
{
S.bottom = (ElemType *)realloc((STACK_SIZE+STACKCREMENT)*sizeof(ElemType)); // 追加存储空间
if(!S.bottom)
return false;
S.top = S.bottom + S.stacksize;
S.stacksize += STACKCREMENT;
}
*S.top = e; // e称为新的栈顶
S.top++; // 栈顶增加1
return true;
}
元素出栈
bool pop(SqStack S, ElemType *e)
{
if(S.top==S.bottom) // 空栈,无法出栈操作
return false;
S.top--;
*e = *(S.top);
return true;
}
1.2.2 栈的静态顺序存储表示
1.2.1.1 实现要点:
- 栈底固定不变;栈顶指针
top
(指示当前栈顶位置)随着进栈和出栈操作而变化。 - 栈空条件:
top=0
表时栈空的初始状态 - 节点进栈:首先执行
top++
,top
指向栈顶位置,然后将数据保存到栈顶(top指向的当前位置)。 - 节点出栈:首先将栈顶元素取出,然后
top--
,指向新的栈顶位置。 - 栈满条件:
top==Maxsize-1
1.2.2.2 实现code
栈的类型定义
#define MAX_SIZE 100 // stack大小
typedef int ElemenType;
typedef struct sqstack{
ElemType stack_array[MAX_SIZE];
int top;
}SqStack;
栈的初始化
SqStack Init_Stack(void)
{
SqStack S;
S.top = 0;
return S;
}
元素进栈
bool push(SqStack S, ElemType e)
{
if(S.top==MAX_SIZE-1) // 栈满,返回错误,上溢出,设法避免
return false;
S.top++; // 栈顶指针加1
S.stack_array[S.top] = e; // e称为新的栈顶
return true;
}
栈满时进栈运算会产生空间溢出(上溢出),是一种出错状态,应设法避免。
元素出栈
bool pop(SqStack S, ElemType *e)
{
if(S.top==0) // 空栈,会产生下溢出错误
return false;
*e = S.stack_array[S.top];
S.top--;
return true;
}
栈空时做出栈会产生溢出(下溢出)。下溢出可能是正常现象,因为在栈使用时,其初态或终态都是空栈,所以下溢出常用做控制转移的条件。
1.3 栈的链式存储表示
又称链栈,是运算受限的单链表。其插入和删除操作都只能在表头位置上进行。
由上图可以看出,链栈没必要设置单链表那样的附加头节点,栈顶指针top就是链表的头指针。
1.3.1 节点表示
typedef struct Stack_Node
{
ElemType data;
struct Stack_Node *next;
}Stack_Node;
1.3.2 链栈的基本操作实现
初始化
Stack_Node *Init_Link_Stack(void)
{
Stack_Node *top;
top = (Stack_Node *)malloc(sizeof(Stack_Node));
top->next = NULL;
return top;
}
入栈
bool push(Stack_Node *top, ElemType e)
{
Stack_Node *p = (Stack_Node *)malloc(sizeof(Stack_Node));
if(!p)
return false;
p->data = e;
p->next = top->next;
top->next = p;
return true;
}
出栈
bool push(Stack_Node *top, ElemType *e)
{
Stack_Node *p ;
if(top->next==NULL) // 栈空
return false;
p = top->next;
*e = p->data;
top->next = p->next;
free(p);
p = NULL;
return true;
}
1.4 栈的应用
数制转换
十进制整数N向其它进制数d的转换是计算机实现计算的基本问题。
转换法则:
n = (n div d)*d + n mod d
其中:div为整除运算;mod为求余运算。
/* 将十进制整数n转换成d进制数 */
void conversion(int n, int d)
{
SqStack S;
int k, *e;
S = Init_Stack();
while(n > 0)
{
k = n %d;
push(S,k);
n = n/d;
}
// 求出所有余数
while(S.top!=0) // 栈不空时出栈
{
pop(S,e);
printf("%ld", *e);
}
}
括号匹配
思想:从左至右扫描一个字符串或表达式,每个右括号将与最近的那个左括号相匹配。可以将枣苗过程中遇到的左括号存放到堆栈中。每当遇到一个右括号时,就将它与栈顶的左括号(如果存在)相匹配,同时从栈删除该左括号。
SqStack S;
S = Init_Stcak();
int Match_Brackets()
{
char ch,x;
scanf("%c", &ch);
while(asc(ch)!=13)
{
if((ch=='('||ch=='['))
push(S,ch);
else if(ch==']')
{
x = pop(S);
if(x!='[')
{
printf("'['括号不匹配");
return false;
}
}
else if(ch==')')
{
x = pop(S);
if(x!='(')
{
printf("'('括号不匹配");
return false;
}
}
if(S.top!=0)
{
printf("括号数量不匹配");
return false;
}
else
return true;
}
}
栈与递归调用
栈在程序设计语言中实现递归调用。为了保证递归调用正确的进行,系统设立一个“递归工作栈”,作为整个递归调用过程期间使用的数据存储区。
每一层递归包含的信息如:参数、局部变量、上一层返回地址构成一个 “工作记录” 。每进入一层递归,就会产生一个新的工作记录压入栈顶;每退出一层递归,就从栈顶弹出一个工作记录。
从被调函数返回调用函数的一般步骤为:
- 若栈为空,则执行正常返回
- 从栈顶弹出一个工作记录
- 将“工作记录”中的参数值、局部变量赋给相应的变量;读取返回地址
- 将函数值赋给相应的变量
- 转移到返回地址
2 队列
允许在一端(队尾)进行插入,另一端(队首)进行删除的线性表表。具有FIFO(First in first out)的特点。
2.1 队列的顺序表示实现
使用一维数组依次存放队首到队尾各个元素。
#define MAX_SIZE 100
typedef struct queue
{
ElemType Queue_array[MAX_SIZE];
int front;
int rear;
}SqQueue;
2.1.1 队列顺序存储结构下的一些概念
- 初始化:
front=rear=0
- 入队: 将新元素插入rear所指向的位置,然后rear加1
- 出队:删去front所指元素,然后加1并返回被删的元素
- 对列为空:
front=rear
- 队满:
rear=MAX_SIZE-1
或front=rear
在非空队列中,队首元素始终指向队头元素,而队尾指针始终指向队尾元素下一位置。
这种存储结构存在一个问题——假溢出。因为在入队和出队的过程中,头、尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。尽管队列中实际元素个数可能远小于数组大小,但是由于队尾指针已超过向量空间的上界而不能做入队操作。
2.1.2 循环队列
为了充分利用向量空间,避免出现假溢出问题,可以采取循环队列的方式解决。
操作与之前相同,不过当队首、队尾指针指向向量上界(MAX_SIZE-1
)时,其加一结果是指向响亮的下界0。
用模运算可以表示为:
i=(i+1)%MAX_SIZE;
入队时,尾指针向前追赶头指针
出队时,头指针头指针向前追赶尾指针
约定入队前,测试尾指针在循环意义下加1是否等与头指针,如相等认为队满。
- rear所指向的单元始终为空
- 循环队列为空:
front=rear
- 循环队列满:
(rear+1)%MAX_SIZE=front
2.1.3 循环队列的基本操作
初始化
SqQueue Init_CirQueue(void)
{
SqQueue Q;
Q.front=Q.rear=0;
return Q;
}
入队操作
bool Insert_CirQueue(SqQueue Q, ElemType e)
{
if((Q.rear+1)%MAX_SIZE==Q.front)
return false;
Q.Queue_array[Q.rear] = e; // 元素e入队
Q.rear = (Q.rear+1)%MAX_SIZE; // 队尾指针向前移动
return true;
}
出队操作
bool Delete_CirQueue(SqQueue Q, ElemType *x)
{
if(Q.front+1==Q.rear) // 队空
return false;
*x = Q.Queue_array[Q.front];
Q.front = (Q.front+1)%MAX_SIZE; // 队首指针向前移动
return true;
}
2.2 队列的链式存储表示与实现
2.2.1 队列的链式存储结构
队列的链式存储结构建成链队列,限制对表头进行删除操作和表尾进行插入操作的链表。
需要用到两种不同的节点:数据元素节点、队首指针和队尾指针的节点。
数据元素节点:
typedef struct Qnode
{
ElemType data;
struct Qnode *next;
};
指针节点:
typedef struct link_queue
{
Qnode *front, *rear;
}Link_Queue;
2.2.2 链表的接本操作
链队列初始化
LinkQueue *Init_LinkQueue(void)
{
LinkQueue *Q;
QNode *p;
p = (QNode *)malloc(sizepf(QNode)); // 开辟头节点
p->next = NULL;
Q = (LinkQueue *)malloc(sizeof(LinkQueue)); // 链队的指针节点
Q.front=Q.rear=p;
return Q;
}
链队列的入队
bool Insert_CirQueue(LinkQueue *Q, ElemType e)
{
QNode *p = (QNode *)malloc(sizeof(QNode));
if(!p)
return false;
p->data = e;
p->next = NULL;
Q.rear->next=p;
Q.rear = p; // 新节点插入到队尾
return true;
}
链队列的出队
bool Del_CirQueue(LinkQueue *Q, ElemType *e)
{
QNode *p;
if(Q.front==Q.rear)
return false;
p = Q.front->next; // 取队首节点
*e = p->data;
Q.front->next = p->next; // 修改队首指针
if(p==Q.rear) // 当队列只有一个节点时应防止丢失队尾指针
Q.rear = Q.front;
free(p);
p=NULL;
return true;
}
链队列的撤销
void Destory_LinkQueue(LinkQueue *Q)
{
while(Q.front!=NULL)
{
Q.rear=Q.front->next; // 令队尾指针指向队列的第一个节点
free(Q.front); // 每次释放一个节点 ,第一次是头节点,以后是元素节点
Q.front=Q.rear;
}
}