1. 单链表企业版
在企业版的单链表中,我们的每个结点只需要维护指针域,而数据域交还给用户自己进行维护,用户在使用的时候需要预留前四个字节由底层使用。这个方法主要用于自定义数据。对于企业版的单链表的类型定义如下:
// 结点,只需要维护指针域,数据域由用户维护
struct LinkNode
{
struct LinkNode *next;
};
// 单链表表头
struct _LinkList
{
struct LinkNode header;
unsigned int size;
};
// 定义单链表类型使结构体内的成员对外不可见
typedef struct _LinkList *LinkList;
在我们定义的结构体数据中,我们需要自己在结构体定义开始的时候定义四个字节的内存空间,以方便单链表的使用。对于单链表的实现,跟前面单链表的实现一样,下面我们直接给出接口模块和实现代码:
接口模块:
// 初始化单链表
LinkList initLinkList(void);
// 插入元素
void insertLinkList(LinkList list, int pos, void *data);
// 按位删除元素
void deleteByPositionLinkList(LinkList list, int pos);
// 按值删除元素
void deleteByValueLinkList(LinkList list, void *data, int(*cmp)(void *, void *));
// 遍历单链表
void foreachLinkList(LinkList list, void(*func)(void *));
// 获取单链表的长度
int getLengthLinkList(LinkList list);
// 清空单链表
void clearLinkList(LinkList list);
// 摧毁单链表
void destroyLinkList(LinkList list);
代码实现:
#include "LinkList.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// 初始化单链表
LinkList initLinkList(void)
{
// 开辟表头空间
struct _LinkList *_linklist = malloc(sizeof(struct _LinkList));
if (_linklist == NULL)
{
return NULL;
}
_linklist->header.next = NULL;
_linklist->size = 0;
return _linklist;
}
// 插入元素
void insertLinkList(LinkList list, int pos, void *data)
{
if (list == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
// 位置不合法默认插入链表尾
if (pos < 0 || pos > (int)_linklist->size)
{
pos = _linklist->size;
}
struct LinkNode *pPreInsertNode = &_linklist->header;
// 查找插入元素的前驱结点
for (unsigned int i = 0; i < (unsigned int)pos; i++)
{
pPreInsertNode = pPreInsertNode->next;
}
// 插入结点
struct LinkNode *pInsertNode = (struct LinkNode *)data;
pInsertNode->next = pPreInsertNode->next;
pPreInsertNode->next = pInsertNode;
_linklist->size++;
}
// 按位删除元素
void deleteByPositionLinkList(LinkList list, int pos)
{
if (list == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
if (pos < 0 || pos > (int)_linklist->size - 1)
{
return;
}
struct LinkNode *pPreDeleteNode = &_linklist->header;
// 找到删除的位置的前驱
for (unsigned int i = 0; i < (unsigned int)pos; i++)
{
pPreDeleteNode = pPreDeleteNode->next;
}
pPreDeleteNode->next = pPreDeleteNode->next->next;
_linklist->size--;
}
// 按值删除元素
void deleteByValueLinkList(LinkList list, void *data, int(*cmp)(void *, void *))
{
if (list == NULL)
{
return;
}
if (data == NULL)
{
return;
}
if (cmp == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
struct LinkNode *pCurrentNode = &_linklist->header;
// 根据条件找到值并删除
for (unsigned int i = 0; i < _linklist->size; i++)
{
pCurrentNode = pCurrentNode->next;
if (cmp(data, pCurrentNode))
{
deleteByPositionLinkList(list, i);
}
}
}
// 遍历单链表
void foreachLinkList(LinkList list, void(*func)(void *))
{
if (list == NULL)
{
return;
}
if (func == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
struct LinkNode *pCurrentNode = &_linklist->header;
for (unsigned int i = 0; i < _linklist->size; i++)
{
pCurrentNode = pCurrentNode->next;
func(pCurrentNode);
}
}
// 获取单链表的长度
int getLengthLinkList(LinkList list)
{
if (list == NULL)
{
return -1;
}
return ((struct _LinkList *)list)->size;
}
// 清空单链表
void clearLinkList(LinkList list)
{
if (list == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
_linklist->header.next = NULL;
_linklist->size = 0;
}
// 摧毁单链表
void destroyLinkList(LinkList list)
{
if (list == NULL)
{
return;
}
struct _LinkList *_linklist = (struct _LinkList *)list;
clearLinkList(list);
free(_linklist);
_linklist = NULL;
}
接下里进行代码测试。代码的测试中我们定义了一个结构体struct Person
,结构体定义如下:
struct Person
{
void *node;
char name[64];
unsigned int age;
} ;
其中node
就是四个字节的空间,方便链表的使用。测试代码如下:
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include "LinkList.h"
struct Person
{
void *node;
char name[64];
unsigned int age;
} ;
// 自定义输出方法
void printPerson(struct Person *person)
{
printf("(%s, %u) ", person->name, person->age);
}
// 自定义比较方法
int isEqual(struct Person *p1, struct Person *p2)
{
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
int main(int argc, char* argv[])
{
struct Person p1 = { NULL, "张三", 65 };
struct Person p2 = { NULL, "小叶", 46 };
struct Person p3 = { NULL, "小长", 78 };
struct Person p4 = { NULL, "小青", 43 };
struct Person p5 = { NULL, "许仙", 42 };
struct Person p6 = { NULL, "小白", 43 };
LinkList list = initLinkList();
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
insertLinkList(list, 0, &p1);
insertLinkList(list, -1, &p2);
insertLinkList(list, 2, &p3);
insertLinkList(list, 3, &p4);
insertLinkList(list, 4, &p5);
insertLinkList(list, 4, &p6);
foreachLinkList(list, printPerson);
putchar('\n');
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
deleteByPositionLinkList(list, 4);
deleteByPositionLinkList(list, 0);
deleteByPositionLinkList(list, 3);
foreachLinkList(list, printPerson);
putchar('\n');
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
deleteByValueLinkList(list, &p2, isEqual);
foreachLinkList(list, printPerson);
putchar('\n');
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
clearLinkList(list);
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
insertLinkList(list, 0, &p2);
insertLinkList(list, 1, &p3);
insertLinkList(list, 2, &p4);
foreachLinkList(list, printPerson);
putchar('\n');
printf("单链表的长度: %d\n", getLengthLinkList(list));
printf("============================================\n");
destroyLinkList(list);
list = NULL;
system("pause");
return 0;
}
#endif
运行结果如下:
2. 栈
栈是一种受限的线性表,只能在线性表的一端进行插入或者删除。
2.1 顺序栈
在顺序栈中,我们将顺序表的低地址作为栈底,将顺序表的高地址作为栈顶。这样选择的是因为栈顶元素出栈的时候可以不用移动大量的元素。
顺序栈的结构体类型、接口以及实现如下:
顺序栈类型的定义以及接口:
#pragma once
#define MAX_SIZE 1024
// 定义栈
struct _Stack
{
void *_data[MAX_SIZE];
unsigned int _size;
};
typedef void * Stack;
// 初始化栈
Stack initStack(void);
// 入栈
void pushStack(Stack stack, void *data);
// 出栈
void popStack(Stack stack);
// 获取栈顶元素
void *getTopStack(Stack stack);
// 获取栈大小
int getSizeStack(Stack stack);
// 判断栈是否为空
int isEmptyStack(Stack stack);
// 摧毁栈
void destroyStack(Stack stack);
顺序栈的实现:
#include "SeqStack.h"
#include <stdio.h>
#include <stdlib.h>
// 初始化栈
Stack initStack(void)
{
struct _Stack *_seqstack = malloc(sizeof(struct _Stack));
_seqstack->_size = 0;
return _seqstack;
}
// 入栈
void pushStack(Stack stack, void *data)
{
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct _Stack *_stack = (struct _Stack *)stack;
// 存不下的情况
if (_stack->_size == MAX_SIZE)
{
return;
}
// 入栈插入数组尾
_stack->_data[_stack->_size] = data;
_stack->_size++;
}
// 出栈
void popStack(Stack stack)
{
if (stack == NULL)
{
return;
}
// 没有元素的情况
if (isEmptyStack(stack))
{
return;
}
struct _Stack *_stack = (struct _Stack *)stack;
// 删除数组尾元素
_stack->_size--;
}
// 获取栈顶元素
void *getTopStack(Stack stack)
{
if (stack == NULL)
{
return NULL;
}
if (isEmptyStack(stack))
{
return NULL;
}
struct _Stack *_stack = (struct _Stack *)stack;
return _stack->_data[_stack->_size - 1];
}
// 获取栈大小
int getSizeStack(Stack stack)
{
if (stack == NULL)
{
return -1;
}
return ((struct _Stack *)stack)->_size;
}
// 判断栈是否为空
int isEmptyStack(Stack stack)
{
if (stack == NULL)
{
return -1;
}
return ((struct _Stack *)stack)->_size == 0;
}
// 摧毁栈
void destroyStack(Stack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
接下来测试以下顺序栈,测试代码如下:
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include "SeqStack.h"
struct Person
{
void *node;
char name[64];
unsigned int age;
};
// 自定义输出方法
void printPerson(struct Person *person)
{
printf("(%s, %u) ", person->name, person->age);
}
int main(int argc, char* argv[])
{
struct Person p1 = { NULL, "张三", 65 };
struct Person p2 = { NULL, "小叶", 46 };
struct Person p3 = { NULL, "小长", 78 };
struct Person p4 = { NULL, "小青", 43 };
struct Person p5 = { NULL, "许仙", 42 };
struct Person p6 = { NULL, "小白", 43 };
Stack stack = initStack();
printf("栈中的元素个数: %d\n", getSizeStack(stack));
printf("栈是否为空: %s\n", isEmptyStack(stack) ? "是" : "否");
pushStack(stack, &p1);
pushStack(stack, &p2);
pushStack(stack, &p3);
pushStack(stack, &p4);
pushStack(stack, &p5);
pushStack(stack, &p6);
while (!isEmptyStack(stack))
{
printPerson(getTopStack(stack));
popStack(stack);
}
putchar('\n');
printf("栈中的元素个数: %d\n", getSizeStack(stack));
printf("栈是否为空: %s\n", isEmptyStack(stack) ? "是" : "否");
system("pause");
return 0;
}
#endif
测试结果如下:
2.2 链式栈
链式栈是使用链表实现的。在链式表的实现中,我们将链表头作为栈顶,将链表尾作为栈底。这样做的好处是每次出栈或者入栈的时候都需要大量的遍历链表。
链式栈的结构体类型、接口以及实现如下:
链式栈的结构体类型与接口:
#pragma once
struct _LinkNode
{
struct _LinkNode *_next;
};
// 定义栈
struct _Stack
{
struct _LinkNode head;
unsigned int _size;
};
typedef void * Stack;
// 初始化栈
Stack initStack(void);
// 入栈
void pushStack(Stack stack, void *data);
// 出栈
void popStack(Stack stack);
// 获取栈顶元素
void *getTopStack(Stack stack);
// 获取栈大小
int getSizeStack(Stack stack);
// 判断栈是否为空
int isEmptyStack(Stack stack);
// 摧毁栈
void destroyStack(Stack stack);
链式栈的实现:
#include "LinkStack.h"
#include <stdio.h>
#include <stdlib.h>
// 初始化栈
Stack initStack(void)
{
struct _Stack *_stack = malloc(sizeof(struct _Stack));
_stack->head._next = NULL;
_stack->_size = 0;
return _stack;
}
// 入栈
void pushStack(Stack stack, void *data)
{
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct _Stack *_stack = (struct _Stack *)stack;
struct _LinkNode *pPushNode = (struct _LinkNode *)data;
// 头插
pPushNode->_next = _stack->head._next;
_stack->head._next = pPushNode;
_stack->_size++;
}
// 出栈
void popStack(Stack stack)
{
if (stack == NULL)
{
return;
}
// 栈为空
if (isEmptyStack(stack))
{
return;
}
struct _Stack *_stack = (struct _Stack *)stack;
_stack->head._next = _stack->head._next->_next;
_stack->_size--;
}
// 获取栈顶元素
void *getTopStack(Stack stack)
{
if (stack == NULL)
{
return NULL;
}
if (isEmptyStack(stack))
{
return NULL;
}
return ((struct _Stack *)stack)->head._next;
}
// 获取栈大小
int getSizeStack(Stack stack)
{
if (stack == NULL)
{
return -1;
}
return ((struct _Stack *)stack)->_size;
}
// 判断栈是否为空
int isEmptyStack(Stack stack)
{
if (stack == NULL)
{
return -1;
}
return ((struct _Stack *)stack)->_size == 0;
}
// 摧毁栈
void destroyStack(Stack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
接下来测试以下链式栈,测试代码如下:
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include "LinkStack.h"
struct Person
{
void *node;
char name[64];
unsigned int age;
};
// 自定义输出方法
void printPerson(struct Person *person)
{
printf("(%s, %u) ", person->name, person->age);
}
int main(int argc, char* argv[])
{
struct Person p1 = { NULL, "张三", 65 };
struct Person p2 = { NULL, "小叶", 46 };
struct Person p3 = { NULL, "小长", 78 };
struct Person p4 = { NULL, "小青", 43 };
struct Person p5 = { NULL, "许仙", 42 };
struct Person p6 = { NULL, "小白", 43 };
Stack stack = initStack();
printf("链式栈中的元素个数: %d\n", getSizeStack(stack));
printf("链式栈是否为空: %s\n", isEmptyStack(stack) ? "是" : "否");
pushStack(stack, &p1);
pushStack(stack, &p2);
pushStack(stack, &p3);
pushStack(stack, &p4);
pushStack(stack, &p5);
pushStack(stack, &p6);
while (!isEmptyStack(stack))
{
printPerson(getTopStack(stack));
popStack(stack);
}
putchar('\n');
printf("链式栈中的元素个数: %d\n", getSizeStack(stack));
printf("链式栈是否为空: %s\n", isEmptyStack(stack) ? "是" : "否");
system("pause");
return 0;
}
#endif
运行结果如下:
3. 栈的应用
栈是一种先进后出的结构。这里的应用主要以括号的匹配为准。假如有个字符串序列为"1+(2+3)*3+3/3-(2*6+8+2)"
。
检测的方法主要想法为:我们从第一个字符开始扫描所有的字符。如果遇到普通字符,则直接忽略掉。如果我们遇到了左括号,则直接入栈。当我们遇到了右括号的时候,如果栈非空,则直接出栈;如果栈为空,则结束扫描并且报错。当所有的字符都扫描完之后,如果栈非空,则报错;如果栈为空,则说明匹配成功。
我们将之前写的顺序栈写成动态库,关于该应用例子代码如下:
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include "SeqStack.h"
#pragma comment(lib, "./SeqStack.lib")
int isLeft(char *ch)
{
return *ch == '(';
}
int isRight(char *ch)
{
return *ch == ')';
}
void printError(char *str, char *errMsg, char *pos)
{
printf("%s\n", errMsg);
printf("%s\n", str);
unsigned int spacePosition = pos - str;
for (unsigned int i = 0; i < spacePosition; i++)
{
putchar(' ');
}
printf("|\n");
}
void match(char *str)
{
if (str == NULL)
{
return;
}
// 初始化栈
Stack stack = initStack();
char *tmpCh = str;
while (*tmpCh)
{
// 左括号入栈
if (isLeft(tmpCh))
{
pushStack(stack, tmpCh);
}
// 右括号出栈
if (isRight(tmpCh))
{
// 栈为空则说明缺少左括号与右括号匹配
if (isEmptyStack(stack))
{
printError(str, "右括号不匹配左括号", tmpCh);
break;
}
else
{
popStack(stack);
}
}
tmpCh++;
}
// 栈为空且匹配到字符串末尾则成功匹配
if (isEmptyStack(stack) && *tmpCh == '\0')
{
printf("括号完全匹配\n");
}
else
{
// 匹配完栈非空则左括号多余
while (!isEmptyStack(stack))
{
tmpCh = getTopStack(stack);
printError(str, "左括号不匹配右括号", tmpCh);
popStack(stack);
}
}
destroyStack(stack);
}
int main(int argc, char* argv[])
{
char *str1 = "(((()()()()())))))";
char *str2 = "((()))(((";
char *str3 = "()()()()()()";
printf("======str1==============\n");
match(str1);
printf("======str2==============\n");
match(str2);
printf("======str3==============\n");
match(str3);
system("pause");
return 0;
}
#endif
运行结果为:
除了这个之外,我们还可以使用栈完成中缀表达式转后缀表达式以及后缀表达式运算的例子。
解决中缀表达式转后缀表达式,首先我们要遍历中缀表达式,如果遇到了数字,则直接输出。对于符号的情况,我们遇到左括号就直接入栈。如果遇到运算符号,则需要与栈顶的优先级进行比较。若栈顶符号的优先级比此符号低,则符号直接入栈;若栈顶符号的优先级比此符号低,则直接出栈然后再此入栈。如果遇到了右括号,将栈顶符号出栈,直到遇到了左括号,同时将左括号与右括号舍弃。
对于后缀表达式的计算,遇到数字就直接进栈,如果遇到了符号就从栈中入栈两个操作数,第一个作为右操作数,第二个作为左操作数,并且将计算的结果入栈。遍历完之后栈中的唯一一个数字即为运算结果。