栈:
栈是一种特殊的线性表,并且只允许在固定的一端进行插入/删除操作,这一端叫做栈顶,另一端叫做栈底。删除/插入数据遵循LIFO(后进先出)原则。一般涉及两个操作:
压栈:将数据插入栈的操作,数据插入在栈顶;
出栈:将数据删除的操作,删除栈顶的数据。
栈的实现:
同顺序表一样,用数组实现栈的时也分为静态数组和动态数组实现栈,在这里使用动态数组实现。
typedef int MSDataType; typedef struct MyStack { MSDataType* arr; int head; int capacity; }Stack;
栈的基本接口:
// 初始化栈 void StackInit(Stack* ps); // 入栈 void StackPush(Stack* ps, MSDataType data); // 出栈 void StackPop(Stack* ps); // 获取栈顶元素 MSDataType StackTop(Stack* ps); // 获取栈中有效元素个数 int StackSize(Stack* ps); // 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 int StackEmpty(Stack* ps); // 销毁栈 void StackDestroy(Stack* ps);
各个接口实现如下:
//初始化栈
void StackInit(Stack* ps) {
assert(ps);
ps->arr = NULL;
ps->capacity = ps->head = 0;
}
//入栈
void StackPush(Stack* ps, MSDataType data) {
//判断容量
if (ps->capacity == ps->head) {
//如果capacity为0,说明栈没有空间或者说只初始化并没有开辟空间
// 而capacity为4,说明已经初始化且开辟空间,只是空间已满。
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->arr = (MSDataType*)realloc(ps->arr, newCapacity * sizeof(MSDataType));
if (ps->arr == NULL) {
printf("realloc fail;\n");
exit(-1);
}
ps->capacity = newCapacity;
}
//入栈:
ps->arr[ps->head] = data;
ps->head++;
}
//出栈
void StackPop(Stack* ps) {
assert(ps);
assert(ps->head > 0);
//这里的打印要注意head下标的问题,数组下标从0开始,
//而head只是记录数组元素个数
printf("%d \n", ps->arr[ps->head - 1]);
ps->head--;
}
//获取栈顶元素
MSDataType StackTop(Stack* ps) {
assert(ps);
assert(ps->head > 0);
return ps->arr[ps->head-1];
}
//获取有效元素个数
int StackSize(Stack* ps) {
assert(ps);
assert(ps->head > 0);
return ps->head;
}
// 检测栈是否为空,如果为不空返回1,如果为空返回0
int StackEmpty(Stack* ps) {
assert(ps);
if (ps->head == 0)
return 0;
else
return 1;
}
//销毁栈
void StackDestroy(Stack* ps) {
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->head = 0;
}
int main() {
Stack stack;
StackInit(&stack);
printf("入栈:\n");
StackPush(&stack, 99);
StackPush(&stack, 88);
StackPush(&stack, 77);
StackPush(&stack, 66);
StackPush(&stack, 55);
printf("出栈:\n");
StackPop(&stack);
StackPop(&stack);
StackPop(&stack);
printf("获取栈顶元素:\n");
printf("%d \n", StackTop(&stack));
printf("获取有效元素个数:\n");
printf("%d \n", StackSize(&stack));
printf("检测栈是否为空:\n");
if (StackEmpty(&stack))
printf("栈不为空;\n");
else
printf("栈为空;\n");
return 0;
}
需要注意的是:在涉及到head操作的时候要特别注意,head表示栈当前元素的个数,也表示栈顶元素相对栈的位置,也就是说head要比对应元素下标大一,因此在打印或获取元素时要对head进行减一,防止访问不正确
更直观的感受如下,入栈放在数组尾部,出栈也是从数组尾部出。
队列:
队列是一种只允许在一端进行插入数据,另一端进行删除数据的线性表,队列有FIFO(先进先出)原则,其中入队列的一端叫队尾,出队列一端叫队头。
使用链表实现队列:
如上图,在使用链表实现时,不仅要考虑队列的实现,也要考虑队列内数据的实现,因此就要定义两个结构体:一个实现队列,一个实现链表;
//单个数据的结构 typedef int MQDataType; typedef struct MyQueueNode { MQDataType val; struct QueueNode* next; }QueueNode; //队列的结构 typedef struct Queue { QueueNode* head; QueueNode* tail; }Queue;
队列的一般接口如下:
// 初始化队列 void QueueInit(Queue* q); // 队尾入队列 void QueuePush(Queue* q, MQDataType data); // 队头出队列 void QueuePop(Queue* q); // 获取队列头部元素 MQDataType QueueFront(Queue* q); // 获取队列队尾元素 MQDataType QueueBack(Queue* q); // 获取队列中有效元素个数 int QueueSize(Queue* q); // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* q); // 销毁队列 void QueueDestroy(Queue* q);
队列接口的实现:
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->head = q->tail = NULL;
}
// 队尾入队列
void QueuePush(Queue* q, MQDataType data) {
assert(q);
QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode));
if (node == NULL) {
printf("malloc fail;\n");
exit(-1);
}
else {
node->next = NULL;
node->val = data;
}
if (q->head == NULL) {
q->head = q->tail = node;
}
else {
q->tail->next = node;
q->tail = node;
}
}
// 队头出队列
void QueuePop(Queue* q) {
assert(q);
assert(q->head != NULL);
if(q->head->next==NULL){
free(q->head);
q->head=q->tail=NULL;
}else{
QueueNode* node = q->head->next;
free(q->head);
q->head = node;
}
}
// 获取队列头部元素
MQDataType QueueFront(Queue* q) {
assert(q);
assert(q->head);
return q->head->val;
}
// 获取队列队尾元素
MQDataType QueueBack(Queue* q) {
assert(q);
assert(q->head);
return q->tail->val;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(q);
int size = 0;
QueueNode* tmp = q->head;
while (tmp != NULL) {
size++;
tmp = tmp->next;
}
return size;
}
// 检测队列是否为空,如果为空返回零,如果非空返回1
int QueueEmpty(Queue* q) {
assert(q);
if (q->head == NULL)
return 0;
else
return 1;
}
// 销毁队列
void QueueDestroy(Queue* q) {
assert(q);
while (q->head != NULL) {
QueueNode* tmp = q->head->next;
free(q->head);
q->head = tmp;
}
q->head = q->tail = NULL;
free(q);
q = NULL;
}
int main() {
Queue queue;
QueueInit(&queue);
printf("队尾入队列:\n");
QueuePush(&queue, 99);
QueuePush(&queue, 88);
QueuePush(&queue, 77);
printf("队头出队列:\n");
QueuePop(&queue);
QueuePop(&queue);
QueuePop(&queue);
QueuePop(&queue);
printf("获取队头数据:\n");
printf("%d\n", QueueFront(&queue));
printf("获取队尾数据:\n");
printf("%d\n", QueueBack(&queue));
printf("有效数据个数:\n");
printf("%d\n", QueueSize(&queue));
printf("判断队列是否为空:\n");
if (QueueEmpty(&queue))
printf("队列不为空\n");
else
printf("队列为空\n");
printf("队列销毁:\n");
QueueDestroy(&queue);
return 0;
}
需要注意的是,在队列结构中head和tail分别代表队列的头节点和尾节点,在每次crud的时候需要注意对head和tail的更新。