带头双向循环链表
链表是一种数据结构,包括单链表、双链表、带哨兵位的单链表等等。这里实现的是链表数据结构中的最优形式:带头双向循环链表。
头文件声明
typedef int DataType;
typedef struct ListNode
{
struct ListNode* prev;
DataType data;
struct ListNode* next;
}LTNode;
LTNode* ListInit();
LTNode* BuyLTNode(DataType x);
void ListPrint(LTNode* phead);
bool ListIsEmpty(LTNode* phead);
//链表的尾插头插
void ListPushBack(LTNode* phead, DataType x);
void ListPushFront(LTNode* phead, DataType x);
//链表的尾删头删
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
//查找
LTNode* ListFind(LTNode* phead, DataType x);
//链表在pos位置之前插入
void ListInsert(LTNode* pos, DataType x);
//链表删除在pos位置的节点
void ListErase(LTNode* pos);
链表添加一个节点并对节点赋值
LTNode* BuyLTNode(DataType x)
{
LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->prev = NULL;
tmp->next = NULL;
return tmp;
}
链表初始化,哨兵节点的data赋值-1
LTNode* ListInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
判断链表是否为空,是则返回true,不是则返回false
bool ListIsEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
链表的增删查改
链表的尾头插
void ListPushBack(LTNode* phead, DataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = phead->prev;
phead->prev = newnode;
newnode->next = phead;
newnode->prev = prev;
prev->next = newnode;
/*phead->prev->next = newnode;
newnode->prev = phead->prev;
phead->prev = newnode;
newnode->next = phead;*/
/*ListInsert(phead, x);*/
}
链表的头插
void ListPushFront(LTNode* phead, DataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
/*phead->next->prev = newnode;
newnode->next = phead->next;
phead->next = newnode;
newnode->prev = phead;*/
/*ListInsert(phead->next, x);*/
}
链表的尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(!ListIsEmpty(phead));
LTNode* prev = phead->prev;
phead->prev = prev->prev;
prev->prev->next = phead;
free(prev);
/*phead->prev = phead->prev->prev;
free(phead->prev->next);
phead->prev->next = phead;*/
/*ListErase(phead->prev);*/
}
链表的头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListIsEmpty(phead));
LTNode* next = phead->next;
phead->next = next->next;
next->next->prev = phead;
free(next);
/*phead->next = phead->next->next;
free(phead->next->prev);
phead->next->prev = phead;*/
/*ListErase(phead->next);*/
}
查找链表元素,找到则返回节点地址,找不到则返回NULL
LTNode* ListFind(LTNode* phead, DataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
//找不到返回NULL
return NULL;
}
链表在pos位置之前插入
void ListInsert(LTNode* pos, DataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
pos->prev = newnode;
newnode->next = pos;
/*pos->prev->next = newnode;
newnode->prev = pos->prev;
pos->prev = newnode;
newnode->next = pos;*/
}
链表删除在pos位置的节点
void ListErase(LTNode* pos)
{
assert(pos);
assert(!ListIsEmpty(pos));
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
/*pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);*/
}
特别地,带头双向循环链表只要实现了链表在pos位置之前插入和链表删除在pos位置的节点这两个功能,就可以实现几乎全部功能,因为头插尾插、头删尾删都可以复用这两个功能。
栈的实现
栈是一种数据结构,其特点是先进后出
头文件声明
typedef int DataType;
typedef struct Stack
{
DataType* arr;
int Top;
int Capacity;
}Stack;
//支持动态增长的栈
//初始化栈
Stack* StackInit();
//检查栈是否为空,如果为空则返回非零,如果不为空则返回0
bool IsEmpty(Stack* ps);
//入栈
void StackPush(Stack* ps, DataType x);
//出栈
void StackPop(Stack* ps);
//获取栈顶元素
DataType StackTop(Stack* ps);
//获取栈中有效元素个数
int StackSize(Stack* ps);
//销毁栈
void StackDestroy(Stack* ps);
初始化栈
Stack* StackInit()
{
Stack* s = (Stack*)malloc(sizeof(Stack));
if (s == NULL)
{
perror("malloc");
exit(-1);
}
s->arr = NULL;
s->Capacity = 0;
s->Top = -1;
return s;
}
检查栈是否为空,如果为空则返回true,如果不为空则返回false
bool IsEmpty(Stack* ps)
{
assert(ps);
return ps->Top == -1;
}
栈的增删查改
入栈
当栈为空时Top等于-1,数组空间不够了就进行2倍扩容。特别注意,有些教科书上将栈的空间定义为Maxsize,不支持扩容,当栈空间满了的时候再入栈就报错。
void StackPush(Stack* ps, DataType x)
{
assert(ps);
if (ps->Top == ps->Capacity - 1)
{
int newcpty = 0;
(ps->arr == NULL) ? (newcpty = 4) : (newcpty = 2 * ps->Capacity);
ps->arr = (DataType*)realloc(ps->arr, newcpty * sizeof(DataType));
if (ps->arr == NULL)
{
perror("realloc");
exit(-1);
}
ps->Capacity = newcpty;
}
ps->arr[++ps->Top] = x;
}
出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!IsEmpty(ps));
ps->Top--;
}
获取栈顶元素
DataType StackTop(Stack* ps)
{
assert(ps);
return ps->arr[ps->Top--];
}
获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->Top + 1;
}
销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
if (ps->arr != NULL)
{
free(ps->arr);
}
free(ps);
}
以上的功能实现起来都是十分简单的,但是如此简单的代码就能实现十分强大的功能。
队列的实现
队列是一种数据结构,其特点是先进先出
头文件声明
typedef int DataType;
typedef struct QueueNode
{
DataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
//初始化队列
Queue* QueueInit();
//检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool IsEmtpy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, DataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列头部元素
DataType QueueFront(Queue* pq);
//获取队列尾部元素
DataType QueueBack(Queue* pq);
//获取队列中有效元素个数
int QueueSize(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
初始化队列
Queue* QueueInit()
{
Queue* pq = (Queue*)malloc(sizeof(Queue));
if (pq == NULL)
{
perror("malloc");
exit(-1);
}
pq->head = NULL;
pq->tail = NULL;
return pq;
}
检测队列是否为空,如果为空返回true,如果非空返回false
bool IsEmtpy(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
队列的增删查改
队尾入队列
void QueuePush(Queue* pq, DataType x)
{
assert(pq);
QueueNode* tmp = (QueueNode*)malloc(sizeof(QueueNode));
if (tmp == NULL)
{
perror("malloc");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
if (pq->tail == NULL)
{
pq->tail = tmp;
pq->head = tmp;
}
else
{
pq->tail->next = tmp;
pq->tail = pq->tail->next;
}
}
队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!IsEmtpy(pq));
if (pq->head == pq->tail)
{
free(pq->head);
pq->head = NULL;
pq->tail = NULL;
}
else
{
QueueNode* prev = pq->head;
pq->head = pq->head->next;
free(prev);
}
}
获取队列头部元素
DataType QueueFront(Queue* pq)
{
assert(pq);
return pq->head->data;
}
获取队列尾部元素
DataType QueueBack(Queue* pq)
{
assert(pq);
return pq->tail->data;
}
获取队列中有效元素个数
在实现获取队列有效元素的个数这个功能时,采用了具有O(n)时间复杂度的算法,这不是一个好的实现方式,特别是在需要频繁调用这个功能的时候,就会有大量的消耗。最好是能够在保证函数接口一种的情况下对这个功能进行优化。
int QueueSize(Queue* pq)
{
assert(pq);
int sz = 0;
QueueNode* cur = pq->head;
while (cur != NULL)
{
sz++;
cur = cur->next;
}
return sz;
}
销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
while (pq->head != NULL)
{
QueuePop(pq);
}
free(pq);
}
写这篇文章的初衷就是将这些数据结构的实现记录下来,作为一个模板。感谢阅读。