目录
一、动态数组
所谓动态数组就是可以自动扩容的数组, 例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//动态数组
struct DynamicArray
{
//数组存储元素的空间首地址,存放的都是地址void*
void **addr;
//最大容量
int capacity;
//实际的元素个数
int size;
};
//初始化
struct DynamicArray *Init_DynamicArray(int capacity)
{
if (capacity < 0)
return NULL;
// 创建动态数组的指针并分配空间
struct DynamicArray *arr = malloc(sizeof(struct DynamicArray));
// 初始化数组初始元素的空间
arr->addr = malloc(capacity * sizeof(void *));
// 初始容量
arr->capacity = capacity;
// 实际的元素个数
arr->size = 0;
return arr;
}
//在指定位置插入元素,如果该位置大于末尾元素的位置,则直接插入到末尾元素之后
void Inseart_DynamicArray(struct DynamicArray *arr, int index, void *value)
{
if (NULL == arr)
return;
if (index <= 0)
return;
// 如果新增的元素位置大于当前元素末尾位置,则直接插入到末尾位置
if (index > arr->size)
{
index = arr->size;
}
if (arr->size >= arr->capacity)
{
// 如果当前元素个数大于当前容量,需要进行扩容,在之前的容量上扩大一倍
int new_capacity = arr->capacity * 2;
void **new_addr = malloc(new_capacity * sizeof(void *));
//拷贝原来的数据到新地址上
memcpy(new_addr, arr->addr, arr->capacity * sizeof(void *));
//释放旧地址
free(arr->addr);
//更新新地址
arr->addr = new_addr;
//更新容量
arr->capacity = new_capacity;
}
//将原生插入到index位置
//插入前需要将index位置之后的所有元素需要移动,从尾巴开始向后逐个元素移动一位,腾出位置来放置index的元素
for (int i = arr->size - 1; i >= index; --i)
{
//让后一个位置等于前一个元素即可
arr->addr[i + 1] = arr->addr[i];
}
//最后将value插入到index位置
arr->addr[index] = value;
//更新元素个数
arr->size++;
}
//遍历
void Foreach_DynamicArray(struct DynamicArray *arr, void (*_callback)(void *))
{
if (NULL == arr)
return;
if (NULL == _callback)
return;
for (int i = 0; i < arr->size; ++i)
{
_callback(arr->addr[i]);
}
}
//按位置删除
void Remove_By_Index(struct DynamicArray *arr, int index)
{
if (NULL == arr)
return;
if (index < 0 || index > arr->size - 1)
return;
for (int i = index; i < arr->size; ++i)
{
//将后一个元素覆盖前一个元素即可
arr->addr[i] = arr->addr[i + 1];
}
//更新size
arr->size--;
}
//按值删除
void Remove_By_Value(struct DynamicArray *arr, void *value, int (*compare)(void *, void *))
{
if (NULL == arr || NULL == value || NULL == compare)
return;
for (int i = 0; i < arr->size; ++i)
{
//因为无法确定数据类型,所以通过回调函数处理比较
if (compare(arr->addr[i], value))
{
Remove_By_Index(arr, i);
break;
}
}
}
//销毁
void Destory_DynamicArray(struct DynamicArray *arr)
{
if (NULL == arr)
return;
if (NULL != arr->addr)
{
free(arr->addr);
arr->addr = NULL;
}
free(arr);
arr = NULL;
}
使用方式如下:
//定义存放的元素
struct Person
{
char name[64];
int age;
};
//打印值得回调函数
void my_callback(void *data)
{
if (NULL == data)
return;
struct Person *p = (struct Person *)data;
printf("name=%s,age=%d\n", p->name, p->age);
}
//比较2个值是否相等的回调函数
int value_compare(void *d1, void *d2)
{
struct Person *p1 = (struct Person *)d1;
struct Person *p2 = (struct Person *)d2;
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
int main()
{
struct DynamicArray *arr = Init_DynamicArray(5);
struct Person p1 = {"aaa", 10};
struct Person p2 = {"bbb", 20};
struct Person p3 = {"ccc", 30};
struct Person p4 = {"ddd", 40};
struct Person p5 = {"eee", 50};
struct Person p6 = {"fff", 60};
Inseart_DynamicArray(arr, 10, &p1);
Inseart_DynamicArray(arr, 10, &p2);
Inseart_DynamicArray(arr, 10, &p3);
Inseart_DynamicArray(arr, 10, &p4);
Inseart_DynamicArray(arr, 10, &p5);
printf("扩容前capacity:%d\n", arr->capacity);
Inseart_DynamicArray(arr, 10, &p6);
printf("扩容后capacity:%d\n", arr->capacity);
Foreach_DynamicArray(arr, my_callback);
printf("删除index=2的元素\n");
Remove_By_Index(arr, 2);
printf("删除后的数组为:\n");
Foreach_DynamicArray(arr, my_callback);
printf("按值删除{ aaa, 10 }\n");
struct Person p_del = {"aaa", 10};
Remove_By_Value(arr, &p_del, value_compare);
printf("删除后的数组为:\n");
Foreach_DynamicArray(arr, my_callback);
system("pause");
return 0;
}
输出结果:
扩容前capacity:5
扩容后capacity:10
name=aaa,age=10
name=bbb,age=20
name=ccc,age=30
name=ddd,age=40
name=eee,age=50
name=fff,age=60
删除index=2的元素
删除后的数组为:
name=aaa,age=10
name=bbb,age=20
name=ddd,age=40
name=eee,age=50
name=fff,age=60
按值删除{ aaa, 10 }
删除后的数组为:
name=bbb,age=20
name=ddd,age=40
name=eee,age=50
name=fff,age=60
二、单向链表
有2种方案,分别是将数据保存到链表节点中和将链表节点保存到数据中,下面会分别介绍
方案一:
先创建链表的头文件LinkList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
extern "C"
{
#endif
// 定义类型
// 用LinkList替代void*,可以将struct LList保户起来(struct LList定义在实现文件中,它代表链表本身),不让外界看到.
typedef void *LinkList;
typedef void(FOREACH_CALLBACK)(void *); //遍历的回调函数
typedef int(COMPARE_CALLBACK)(void *, void *); // 对比的回调函数
//初始化
LinkList Init_LinkList();
//遍历
void Foreach_LinkList(LinkList list, FOREACH_CALLBACK callback);
//插入
void Inseart_LinkList(LinkList list, int index, void *value);
//根据位置删除
void Remove_By_Index(LinkList list, int index);
//根据值删除
void Remove_By_Value(LinkList list, void *value, COMPARE_CALLBACK callback);
//清空列表
void Clear_LinkList(LinkList list);
//销毁
void Destory_LinkList(LinkList list);
//元素个数
int Size_Of_LinkList(LinkList list);
#ifdef __cplusplus
}
#endif
创建链表的实现文件LinkList.c
#include "LinkList.h"
// 代表链表内每个元素的节点结构体
struct LinkNode
{
void *addr; //存放数据的地址
struct LinkNode *next;
};
// 链表的结构体
struct LList
{
struct LinkNode header; // 头结点
int size;
};
// 初始化
LinkList Init_LinkList()
{
struct LList *list = malloc(sizeof(struct LList));
struct LinkNode header;
header.addr = NULL;
header.next = NULL;
list->header = header;
list->size = 0;
return list;
}
// 遍历
void Foreach_LinkList(LinkList linkList, FOREACH_CALLBACK callback)
{
if (NULL == linkList)
return;
struct LList *list = (struct LList *)linkList;
struct LinkNode *curr_node = list->header.next;
while (NULL != curr_node)
{ // 从头节点的next节点开始遍历
callback(curr_node->addr);
// 更新当前node
curr_node = curr_node->next;
}
}
// 按位置插入元素
void Inseart_LinkList(LinkList linkList, int index, void *value)
{
if (NULL == linkList || NULL == value)
return;
// 先强转成内部链表结构体指针类型
struct LList *list = (struct LList *)linkList;
// 得到头结点的结构体指针
struct LinkNode *curr_node = &(list->header);
if (index < 0 || index > list->size)
index = list->size;
//查找要插入的节点的前一个节点
for (int i = 0; i < index; ++i)
{
// 先获取到index的前一个节点
curr_node = curr_node->next;
}
// 创建新节点
struct LinkNode *new_node = malloc(sizeof(struct LinkNode));
new_node->addr = value;
new_node->next = NULL;
// 将当前节点的next节点给新节点的next
new_node->next = curr_node->next;
// 修改当前节点的next节点指向新添加的节点
curr_node->next = new_node;
// 更新个数
list->size++;
}
// 根据位置删除
void Remove_By_Index(LinkList linkList, int index)
{
if (NULL == linkList)
return;
struct LList *list = (struct LList *)linkList;
if (index < 0 || index > list->size - 1)
return;
//先找到要删除节点的前一个节点
struct LinkNode *curr_node = &(list->header);
for (int i = 0; i < index; ++i)
{
// 先获取到index的前一个节点
curr_node = curr_node->next;
}
//修改当前节点的next为要删除节点的next
struct LinkNode *del_node = curr_node->next;
curr_node->next = del_node->next;
//释放删除节点的空间
free(del_node);
del_node = NULL;
//更新个数
list->size--;
}
// 根据值删除
void Remove_By_Value(LinkList linkList, void *value, COMPARE_CALLBACK callback)
{
if (NULL == linkList || NULL == callback || NULL == value)
return;
struct LList *list = (struct LList *)linkList;
// curr_node的上一个节点
struct LinkNode *pre_node = &(list->header);
// curr_node就是要被删除的节点
struct LinkNode *curr_node = pre_node->next;
while (curr_node != NULL)
{
if (callback(curr_node->addr, value))
break;
pre_node = curr_node;
// 不断的更新curr_node
curr_node = curr_node->next;
}
if (curr_node == NULL) //没有找到
return;
//删除当前节点
pre_node->next = curr_node->next;
free(curr_node);
curr_node = NULL;
//更新个数
list->size--;
//还要一种方式是使用for循环找到要删除元素的index,然后调用Remove_By_Index
/*struct LinkNode* curr_node = &(list->header);
for (int i = 0; i < list->size; ++i)
{
curr_node = curr_node->next;
if (callback(curr_node->addr, value))
{
Remove_By_Index(linkList, i);
break;
}
}*/
}
// 清空列表
void Clear_LinkList(LinkList linkList)
{
if (NULL == linkList)
return;
struct LList *list = (struct LList *)linkList;
struct LinkNode *curr_node = list->header.next;
while (NULL != curr_node)
{
//先保存下一个节点
struct LinkNode *next_node = curr_node->next;
//释放当前节点
free(curr_node);
curr_node = next_node;
}
list->size = 0;
list->header.next = NULL;
}
// 销毁
void Destory_LinkList(LinkList list)
{
Clear_LinkList(list);
if (NULL != list)
{
free(list);
list = NULL;
}
}
// 取出链链表的元素个数
int Size_Of_LinkList(LinkList linkList)
{
if (NULL == linkList)
return 0;
struct LList *list = (struct LList *)linkList;
return list->size;
}
最后创建测试文件TestLink.c
#include "LinkList.h"
// 测试数据
struct Person
{
char name[64];
int age;
};
// 遍历回调
void foreach_callback(void *data)
{
if (NULL == data)
return;
struct Person *p = (struct Person *)data;
printf("name=%s,age=%d\n", p->name, p->age);
}
// 删除回调
int remove_compare(void *d1, void *d2)
{
struct Person *p1 = (struct Person *)d1;
struct Person *p2 = (struct Person *)d2;
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
int main()
{
LinkList list = Init_LinkList();
struct Person p1 = {"a", 1};
struct Person p2 = {"b", 2};
struct Person p3 = {"c", 3};
struct Person p4 = {"d", 4};
struct Person p5 = {"e", 5};
struct Person p6 = {"f", 6};
struct Person p7 = {"g", 7};
Inseart_LinkList(list, 0, &p1); // 1
Inseart_LinkList(list, 0, &p2); // 2 1
Inseart_LinkList(list, 1, &p3); // 2 3 1
Inseart_LinkList(list, 2, &p4); // 2 3 4 1
Inseart_LinkList(list, 20, &p5); // 2 3 4 1 5
Inseart_LinkList(list, 3, &p6); // 2 3 4 6 1 5
Inseart_LinkList(list, 6, &p7); // 2 3 4 6 1 5 7
// 遍历元素
Foreach_LinkList(list, foreach_callback);
printf("删除index=2的元素\n");
Remove_By_Index(list, 2);
printf("删除后的列表为:\n");
Foreach_LinkList(list, foreach_callback);
printf("删除{ a, 1 }的元素\n");
Remove_By_Value(list, &p1, remove_compare);
printf("删除后的列表为:\n");
Foreach_LinkList(list, foreach_callback);
//清空
printf("清空");
Clear_LinkList(list);
printf("清空后的元素个数为:%d:\n", Size_Of_LinkList(list));
printf("销毁");
Destory_LinkList(list);
return 0;
}
编译方式如下:
gcc -o test -I . LinkList.c TestLink.c
其中-I(大写的i)表示头文件的目录位置, 后面输入.
表示当前位置, 然后需要同时指定LinkList.c 和TestLink.c文件一起编译,成功后执行test,结果如下:
name=b,age=2
name=c,age=3
name=d,age=4
name=f,age=6
name=a,age=1
name=e,age=5
name=g,age=7
删除index=2的元素
删除后的列表为:
name=b,age=2
name=c,age=3
name=f,age=6
name=a,age=1
name=e,age=5
name=g,age=7
删除{ a, 1 }的元素
删除后的列表为:
name=b,age=2
name=c,age=3
name=f,age=6
name=e,age=5
name=g,age=7
清空清空后的元素个数为:0:
销毁
方案二:
实现思路:通过在用户数据中保存一个关联节点的结构体来实现, 先来看一个简单例子
#include<stdio.h>
#include<stdlib.h>
// 节点结构体
struct LinkNode
{
struct LinkNode * next;
};
// 用户数据
struct Person
{
// 包含LinkNode
struct LinkNode node; // 这里不使用指针类型的好处是方便以后扩展,因为指针的话只有4or8个字节
// 数据...
char name[64];
int age;
};
int main()
{
struct Person p1 = { NULL, "a", 1 };
struct Person p2 = { NULL, "b", 2 };
struct Person p3 = { NULL, "c", 3 };
struct Person p4 = { NULL, "d", 4 };
//通过指针修改步长获取struct Person的前4个字节,也就是struct LinkNode
struct LinkNode *node1 = (struct LinkNode *)&p1;
struct LinkNode *node2 = (struct LinkNode *)&p2;
struct LinkNode *node3 = (struct LinkNode *)&p3;
struct LinkNode *node4 = (struct LinkNode *)&p4;
//进行节点关联
node1->next = node2;//等效于 (*node1).next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//遍历链表
struct LinkNode *curr_node = node1;
while (NULL != curr_node)
{
//重新修改步长为struct Person*类型的步长
struct Person* p = (struct Person*)curr_node;
//输出用户数据
printf("name=%s,age=%d\n", p->name, p->age);
//更新当前节点
curr_node = curr_node->next;
}
return 0;
}
输出结果:
name=a,age=1
name=b,age=2
name=c,age=3
name=d,age=4
有了上面的思想后,下面开始完善一下.
创建头文件
#include<stdio.h>
#include<stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
struct LinkNode //节点
{
struct LinkNode *next; //只需要维护节点即可,不需要维护用户数据
};
typedef void* LinkList; //隐藏LList
//初始化链表
LinkList Init_LinkList();
//遍历
void Foreach_LinkList(LinkList linkList, void(*FOREACH_CALLBACK)(void*));
//插入
void Inseart_By_Index_LinkList(LinkList linkList, int index, void* value);
//根据位置删除
void Remove_By_Index_LinkList(LinkList linkList, int index);
//清空
void Clear_LinkList(LinkList linkList);
//销毁
void Destory_LinkList(LinkList linkList);
#ifdef __cplusplus
}
#endif
创建实现文件
#include "LinkList.h"
struct LList //链表
{
struct LinkNode header; //头结点
int size; //个数
};
//初始化链表
LinkList Init_LinkList()
{
struct LList *list = malloc(sizeof(struct LList));
list->header.next = NULL;
list->size = 0;
return list;
}
//遍历
void Foreach_LinkList(LinkList linkList, void (*FOREACH_CALLBACK)(void *))
{
if (NULL == linkList || NULL == FOREACH_CALLBACK)
return;
struct LList *list = (struct LList *)linkList;
struct LinkNode *curr_node = list->header.next;
while (curr_node)
{
FOREACH_CALLBACK(curr_node);
curr_node = curr_node->next;
}
}
//按位置插入
void Inseart_By_Index_LinkList(LinkList linkList, int index, void *value)
{
if (NULL == linkList || NULL == value)
return;
struct LList *list = (struct LList *)linkList;
if (index < 0 || index > list->size)
index = list->size;
//找到要插入位置的前一个节点
struct LinkNode *curr_node = &(list->header);
for (int i = 0; i < index; ++i)
{
curr_node = curr_node->next;
}
//从数据中获取节点
struct LinkNode *new_node = (struct LinkNode *)value;
//要插入位置的节点next给新节点
new_node->next = curr_node->next;
//要插入位置的前节点的next指向新节点
curr_node->next = new_node;
//更新长度
list->size++;
}
//根据位置删除
void Remove_By_Index_LinkList(LinkList linkList, int index)
{
if (NULL == linkList)
return;
struct LList *list = (struct LList *)linkList;
if (index < 0 || index > list->size - 1)
return;
//获取待删除节点的上一个节点
struct LinkNode *curr_node = &(list->header);
for (int i = 0; i < index; ++i)
{
curr_node = curr_node->next;
}
//获取待删除节点
struct LinkNode *del_node = curr_node->next;
//删除待删除节点,就是将它的前置节点的next指向它的后置节点
curr_node->next = del_node->next;
//更新长度
list->size--;
}
//清空
void Clear_LinkList(LinkList linkList)
{
if (NULL == linkList)
return;
struct LList *list = (struct LList *)linkList;
list->header.next = NULL; //具体的节点由于是用户数据,需要用户自己管理内存
list->size = 0;
}
//销毁
void Destory_LinkList(LinkList linkList)
{
Clear_LinkList(linkList);
if (NULL != linkList)
{
free(linkList);
linkList = NULL;
}
}
测试文件
#include "LinkList.h"
struct Person
{
struct LinkNode linknode; //必须添加节点
char name[64];
int age;
};
// 遍历的回调函数
void Foreach_Callback(void *data)
{
struct Person *p = (struct Person *)data;
printf("name=%s,age=%d\n", p->name, p->age);
}
int main()
{
//创建用户数据
struct Person p1 = {NULL, "a", 1};
struct Person p2 = {NULL, "b", 2};
struct Person p3 = {NULL, "c", 3};
struct Person p4 = {NULL, "d", 4};
struct Person p5 = {NULL, "e", 5};
struct Person p6 = {NULL, "f", 6};
printf("p1首地址%p,首元素地址%p\n", &p1, &(p1.linknode)); // p1首地址5176116,首元素地址5176116
printf("p1首地址%p,首元素地址%p\n", &p2, &(p2.linknode)); // p2首地址5176036,首元素地址5176036,和上一个相差80,刚好是64+8+8,一个Person结构体的大小
//初始化
LinkList list = Init_LinkList();
//插入数据
Inseart_By_Index_LinkList(list, 0, &p1);
Inseart_By_Index_LinkList(list, 0, &p2);
Inseart_By_Index_LinkList(list, 0, &p3);
Inseart_By_Index_LinkList(list, 0, &p4);
Inseart_By_Index_LinkList(list, 0, &p5);
Inseart_By_Index_LinkList(list, 0, &p6);
Foreach_LinkList(list, Foreach_Callback);
printf("删除index=2的元素\n");
Remove_By_Index_LinkList(list, 2);
printf("删除后链表为:\n");
Foreach_LinkList(list, Foreach_Callback);
Destory_LinkList(list);
}
结果如下:
p1首地址0x16b23f1c8,首元素地址0x16b23f1c8
p1首地址0x16b23f178,首元素地址0x16b23f178
name=f,age=6
name=e,age=5
name=d,age=4
name=c,age=3
name=b,age=2
name=a,age=1
删除index=2的元素
删除后链表为:
name=f,age=6
name=e,age=5
name=c,age=3
name=b,age=2
name=a,age=1
2.1 链表逆序
例如将链表为header->A->B->C->D->NULL 逆序变成 header->D->C->B->A->NULL, 直接上代码:
void Reverse_Node_List(struct LinkNode *header)
{
if (NULL == header)
return;
//需要三个指针,当前节点,后续节点,前置节点
struct LinkNode *pre_node = NULL;
struct LinkNode *next_node = NULL;
struct LinkNode *curr_node = header->next;
while(curr_node !=NULL)
{
//先保存next节点
next_node = curr_node->next;
//然后将当前节点的next指向前置节点
curr_node->next = pre_node;
//更新前置节点
pre_node = curr_node;
//更新当前节点
curr_node = next_node;
}
//最后将header指向pre_node
header->next = pre_node;
}
图解:
进入循环前:curr指针指向A、pre指向NULL、next指向NULL
Loop1:将next指针指向B、将A的next指向pre(NULL)、更新pre指向curr(A)、更新curr指向next(B)
Loop2:将next指针指向curr(B)的next(C)、将curr(B)的next指向pre(A)、更新pre指向curr(B)、更新curr指向next(C)
Loop3:将next指针指向curr(C)的next(D)、将curr(C)的next指向pre(B)、更新pre指向curr(C)、更新curr指向next(D)
Loop4:将next指向curr(D)的next(NULL)、将curr(D)的next指向pre(C)、更新pre指向curr(D)、更新curr指向(NULL)
结束循环后:将head指向pre(D)
三、栈
栈的存储特定是 后进先出(LIFO)
3.1 顺序存储
所谓顺序存储就是说该栈是一个连续的内存空间,内部实现是通过数组来实现的.
先定义头文件SeqStack.h
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C"
{
#endif
// 定义数组最大容量
#define MAX 1024
// 声明一个顺序栈
struct SStack
{
void *data[MAX]; // 存放数据地址的数组
int size; // 元素个数
};
// 定义SeqStack类型
typedef void *SeqStack;
// 初始化
SeqStack Init_SeqStack();
// 插入元素
void Inseart_SeqStack(SeqStack seqStack, void *value);
// 获取size
int Size_SeqStack(SeqStack seqStack);
// 出栈
void pop_SeqStack(SeqStack seqStack);
// 获取栈顶元素
void *top_SeqStack(SeqStack seqStack);
// 遍历
void Foreach_SeqStack(SeqStack seqStack, void (*FOREACH_CALLBACK)(void *));
// 销毁
void Destory_SeqStack(SeqStack seqStack);
#ifdef __cplusplus
}
#endif
定义实现文件SeqStack.c
#include "SeqStack.h"
#include <string.h>
// 初始化
SeqStack Init_SeqStack()
{
// 初始化为struct SStack *类型
struct SStack *stack = malloc(sizeof(struct SStack));
memset(stack, 0, sizeof(struct SStack));
stack->size = 0;
return stack;
}
// 获取size
int Size_SeqStack(SeqStack seqStack)
{
if (NULL == seqStack)
return 0;
struct SStack *stack = (struct SStack *)seqStack;
return stack->size;
}
// 插入元素
void Inseart_SeqStack(SeqStack seqStack, void *value)
{
if (NULL == seqStack)
return;
struct SStack *stack = (struct SStack *)seqStack;
// 巧妙的利用size作为当前数组的index
stack->data[stack->size] = value;
// 更新元素个数
stack->size++;
}
// 出栈(删掉栈顶的元素)
void pop_SeqStack(SeqStack seqStack)
{
if (NULL == seqStack)
return;
struct SStack *stack = (struct SStack *)seqStack;
if (stack->size == 0)
return;
// 删掉栈顶的元素
stack->data[stack->size - 1] = NULL;
stack->size--;
}
// 获取栈顶元素
void *top_SeqStack(SeqStack seqStack)
{
if (NULL == seqStack)
return;
struct SStack *stack = (struct SStack *)seqStack;
if (stack->size == 0)
return NULL;
return stack->data[stack->size - 1];
}
// 遍历
void Foreach_SeqStack(SeqStack seqStack, void (*FOREACH_CALLBACK)(void *))
{
if (NULL == seqStack)
return;
struct SStack *stack = (struct SStack *)seqStack;
int size = stack->size;
for (int i = size - 1; i >= 0; --i)
{
void *element = stack->data[i];
printf("遍历元素的index=%d,value=%p\n", i, element);
FOREACH_CALLBACK(element);
}
}
// 销毁
void Destory_SeqStack(SeqStack seqStack)
{
if (NULL == seqStack)
return;
struct SStack *stack = (struct SStack *)seqStack;
free(stack);
stack = NULL;
}
定义测试文件
#include "SeqStack.h"
struct Person
{
char name[64];
int age;
};
void Foreach_Callback(void * d)
{
struct Person *p = (struct Person *)d;
printf("name=%s,age=%d\n", p->name, p->age);
}
int main()
{
struct Person p1 = { "a", 1 };
struct Person p2 = { "b", 2 };
struct Person p3 = { "c", 3 };
struct Person p4 = { "d", 4 };
struct Person p5 = { "e", 5 };
SeqStack stack = Init_SeqStack();
Inseart_SeqStack(stack, &p1);
Inseart_SeqStack(stack, &p2);
Inseart_SeqStack(stack, &p3);
Inseart_SeqStack(stack, &p4);
Inseart_SeqStack(stack, &p5);
int size = Size_SeqStack(stack);
printf("size=%d\n", size);
Foreach_SeqStack(stack, Foreach_Callback);
struct Person * top = top_SeqStack(stack);
printf("top->name=%s,age=%d\n", top->name, top->age);
system("pause");
}
结果:
size=5
遍历元素的index=4,value=0x16bb8b0c4
name=e,age=5
遍历元素的index=3,value=0x16bb8b108
name=d,age=4
遍历元素的index=2,value=0x16bb8b14c
name=c,age=3
遍历元素的index=1,value=0x16bb8b190
name=b,age=2
遍历元素的index=0,value=0x16bb8b1d4
name=a,age=1
top->name=e,age=5
3.2 链式存储
插入的时候只需要往头节点的next插入,取的时候也是一样,这就是后进先出的栈结构了.
先定义头文件LinkStack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#ifdef __cplusplus
extern "C"{
#endif
struct StackNode
{
struct StackNode *next;
};
typedef void * LinkStack;
//初始化
LinkStack Init_LinkStack();
//添加
void Inseart_LinkStack(LinkStack linkStack,void* value);
//出栈
void Pop_LinkStack(LinkStack linkStack);
//获取栈顶元素
void* Top_LinkStack(LinkStack linkStack);
//遍历
void Foreach_LinkStack(LinkStack linkStack, void(*FOREACH_CALLBACK)(void*));
//清空
void Clear_LinkStack(LinkStack linkStack);
//销毁
void Destory_LinkStack(LinkStack linkStack);
#ifdef __cplusplus
}
#endif
定义实现文件LinkStack.c
#include "LinkStack.h"
struct LStack
{
struct StackNode header; // 头节点
int size;
};
//初始化
LinkStack Init_LinkStack()
{
struct LStack *ls = malloc(sizeof(struct LStack));
ls->header.next = NULL;
ls->size = 0;
return ls;
}
//添加
void Inseart_LinkStack(LinkStack linkStack, void *value)
{
if (NULL == linkStack)
return;
struct LStack *ls = (struct LStack *)linkStack;
struct StackNode *first_node = ls->header.next;
//从数据中获取头结点
struct StackNode *new_node = (struct StackNode *)value;
//插入头结点的next位置
ls->header.next = new_node;
//将原来的头节点添加到新插入节点的next
new_node->next = first_node;
}
//出栈
void Pop_LinkStack(LinkStack linkStack)
{
if (NULL == linkStack)
return;
struct LStack *ls = (struct LStack *)linkStack;
if (ls->size == 0)
return;
// pop的操作就是要讲栈顶元素删掉,header阶段是不会删掉的,只能删掉header的next节点
struct StackNode *first_node = ls->header.next;
// 重新赋值header的next节点
ls->header.next = first_node->next;
ls->size--;
}
//获取栈顶元素
void *Top_LinkStack(LinkStack linkStack)
{
if (NULL == linkStack)
return NULL;
struct LStack *ls = (struct LStack *)linkStack;
if (ls->size == 0)
return NULL;
struct StackNode *first_node = ls->header.next;
return first_node;
}
//遍历
void Foreach_LinkStack(LinkStack linkStack, void (*FOREACH_CALLBACK)(void *))
{
if (NULL == linkStack || NULL == FOREACH_CALLBACK)
return;
struct LStack *ls = (struct LStack *)linkStack;
struct StackNode *first_node = ls->header.next;
while (first_node)
{
FOREACH_CALLBACK(first_node);
first_node = first_node->next;
}
}
void Clear_LinkStack(LinkStack linkStack)
{
if (NULL == linkStack)
return;
struct LStack *ls = (struct LStack *)linkStack;
// 只需要断开头节点的next即可
ls->header.next = NULL;
ls->size = 0;
}
void Destory_LinkStack(LinkStack linkStack)
{
if (NULL == linkStack)
return;
Clear_LinkStack(linkStack);
free(linkStack);
linkStack = NULL;
}
测试
#include "LinkStack.h"
struct Person
{
struct StackNode node; //必须添加节点,因为在添加的时候可用来强转为struct StackNode*类型
char name[64];
int age;
};
void Foreach_Callback(void *data)
{
struct Person * p = (struct Person*)data;
printf("name=%s,age=%d\n", p->name, p->age);
}
int main()
{
//创建用户数据
struct Person p1 = { NULL, "a", 1 };
struct Person p2 = { NULL, "b", 2 };
struct Person p3 = { NULL, "c", 3 };
struct Person p4 = { NULL, "d", 4 };
struct Person p5 = { NULL, "e", 5 };
struct Person p6 = { NULL, "f", 6 };
//初始化
LinkStack stack = Init_LinkStack();
//插入数据
Inseart_LinkStack(stack, &p1);
Inseart_LinkStack(stack, &p2);
Inseart_LinkStack(stack, &p3);
Inseart_LinkStack(stack, &p4);
Inseart_LinkStack(stack, &p5);
Inseart_LinkStack(stack, &p6);
Foreach_LinkStack(stack, Foreach_Callback);
Destory_LinkStack(stack);
return 0;
}
结果如下:
name=f,age=6
name=e,age=5
name=d,age=4
name=c,age=3
name=b,age=2
name=a,age=1
可以看到是一个后进先出的顺序。
四、队列
队列是只允许一端进行插入操作,而另一端进行删除操作的线性表,也就是先进先出(FIFO)的规则,不支持随机存取.
先定义头文件LinkQueue.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct QueueNode
{
struct QueueNode *next;
};
typedef void *LinkQueue;
#ifdef __cplusplus
extern "C"
{
#endif
//初始化
LinkQueue Init_LinkQueue();
//添加到尾部
void push(LinkQueue lq, void *value);
//出栈
void Pop_LinkQueue(LinkQueue lq);
//从头部获取
void *Front_LinkQueue(LinkQueue lq);
//从尾部获取
void *Back_LinkQueue(LinkQueue lq);
//大小
int Size_LinkQueue(LinkQueue lq);
//销毁
void Destory_LinkQueue(LinkQueue lq);
#ifdef __cplusplus
}
#endif
定义实现文件LinkQueue.c
#include "LinkQueue.h"
struct LQueue
{
struct QueueNode header; //头结点
struct QueueNode *tail; //指向尾结点的指针
int size;
};
//初始化
LinkQueue Init_LinkQueue()
{
struct LQueue *queue = malloc(sizeof(struct LQueue));
queue->size = 0;
queue->header.next = NULL;
queue->tail = &(queue->header);
return queue;
}
//添加到尾部
void push(LinkQueue lq, void *value)
{
if (NULL == lq || NULL == value)
return;
struct LQueue *queue = (struct LQueue *)lq;
//从数据中获取新节点
struct QueueNode *new_node = (struct QueueNode *)value;
new_node->next = NULL;
//尾结点指向新节点
queue->tail->next = new_node;
//更新尾结点
queue->tail = new_node;
//if (queue->size == 0)
//{
// 不需要了,因为初始化的时候尾结点是指向头结点的
// queue->header.next = new_node;
//}
//更新个数
queue->size++;
}
//出栈
void Pop_LinkQueue(LinkQueue lq)
{
if (NULL == lq)
return;
struct LQueue *queue = (struct LQueue *)lq;
if (queue->size == 1)
{
queue->header.next = NULL;
queue->tail = &(queue->header);
queue->size = 0;
return;
}
//先获取要出栈的元素
struct QueueNode *first_node = queue->header.next;
//修改头结点的next指向出栈元素的next
queue->header.next = first_node->next;
queue->size--;
}
//从头部获取
void *Front_LinkQueue(LinkQueue lq)
{
if (NULL == lq)
return NULL;
struct LQueue *queue = (struct LQueue *)lq;
if (queue->size == 0)
return NULL;
struct QueueNode *first_node = queue->header.next;
//queue->size--; 不做元素的删除操作
return first_node;
}
//从尾部获取
void *Back_LinkQueue(LinkQueue lq)
{
if (NULL == lq)
return NULL;
struct LQueue *queue = (struct LQueue *)lq;
if (queue->size == 0)
return NULL;
struct QueueNode *last_node = queue->tail;
//queue->size--;不做元素的删除操作
return last_node;
}
//大小
int Size_LinkQueue(LinkQueue lq)
{
if (NULL == lq)
return -1;
struct LQueue *queue = (struct LQueue *)lq;
return queue->size;
}
//销毁
void Destory_LinkQueue(LinkQueue lq)
{
if (NULL == lq)
return;
free(lq);
lq = NULL;
}
测试
#include "LinkQueue.h"
struct Person
{
struct QueueNode node; //必须添加节点
char name[64];
int age;
};
int main()
{
//创建用户数据
struct Person p1 = {NULL, "a", 1};
struct Person p2 = {NULL, "b", 2};
struct Person p3 = {NULL, "c", 3};
struct Person p4 = {NULL, "d", 4};
struct Person p5 = {NULL, "e", 5};
struct Person p6 = {NULL, "f", 6};
LinkQueue queue = Init_LinkQueue();
push(queue, &p1);
push(queue, &p2);
push(queue, &p3);
push(queue, &p4);
push(queue, &p5);
push(queue, &p6);
struct Person *first = Front_LinkQueue(queue);
printf("first->name=%s,age=%d\n", first->name, first->age);
struct Person *last = Back_LinkQueue(queue);
printf("last->name=%s,age=%d\n", last->name, last->age);
while (Size_LinkQueue(queue))
{
printf("first->name=%s,age=%d\n", first->name, first->age);
Pop_LinkQueue(queue);
first = Front_LinkQueue(queue);
}
Destory_LinkQueue(queue);
return 0;
}
结果如下:
first->name=a,age=1
last->name=f,age=6
first->name=a,age=1
first->name=b,age=2
first->name=c,age=3
first->name=d,age=4
first->name=e,age=5
first->name=f,age=6
可以看到是先进先出的顺序结构
五、树和二叉树
5.1 概念
树的定义
由一个或多个节点组成的有限容器,有且仅有一个节点称为根节点root时,其余节点不相交.
特点:
非线性结构,有一个直接的前驱,但可能有多个直接的后继
树具有递归性,树中还有树
树可以为空,即节点个数为0
二叉树的定义
n(n>=0)个节点的有限集合,由一个根节点一级两颗互不相交且分别称为左子树和右子树的二叉树组成.
特点:
每个节点最多只有两颗子树(不存在度大于2的结点)
左子树和右子树次序不能颠倒(有序树)
性质:
在二叉树的第i层上最多有2^(i-1)个结点(i>0);
深度位k的二叉树最多有2^k-1个结点(k>0);
任何一个二叉树,若度(孩子)为2的结点数有n2个,则叶子树(n0)必定为n2+1(即n0 = n2 + 1)
完全二叉树
除最后一层外,每一层上的结点数均达到了最大值,在最后一层缺少右边若干结点.
5.2 二叉树的遍历
1)遍历定义
指按某条搜索路线遍历访问每个结点且不重复(又称周游)
2)遍历方法
牢记一种约定,堆每个结点的查看都是"先左后右"
有3种实现方案:
DLR(先(根)序遍历)–>先根再左再右
LDR(中(根)序遍历)–>先左再根再右
LRD(后(根)序遍历)–>先左再右再根
注:"左, 中, 右"的意思是指访问的结点D是先于子树出现还是后于子树出现.
从递归角度看,这3种算法完全相同或者说这3种遍历算法的访问路径是一样的.
从虚线的出发点到终点的路径上,每个结点经过3次.
第一次经过时访问=先序遍历
第二次经过时访问=中序遍历
第三次经过时访问=后续遍历
每经过一个节点都会判断是否是一个子树,是的话继续前进,相当于递归.
下面开始代码实现上图3种遍例效果
1)先序遍历
#include<stdio.h>
struct BiNode
{
char ch;
struct BiNode* lchild;//左子树
struct BiNode* rchild;//右子树
};
void Foreach_Node(struct BiNode *root)
{
if (NULL == root)
return;
//先序遍历,先根
printf("%c ", root->ch);
//再左
Foreach_Node(root->lchild);
//再右
Foreach_Node(root->rchild);
}
int main()
{
//创建结点
struct BiNode nodeA = { 'A', NULL, NULL };
struct BiNode nodeB = { 'B', NULL, NULL };
struct BiNode nodeC = { 'C', NULL, NULL };
struct BiNode nodeD = { 'D', NULL, NULL };
struct BiNode nodeE = { 'E', NULL, NULL };
struct BiNode nodeF = { 'F', NULL, NULL };
struct BiNode nodeG = { 'G', NULL, NULL };
struct BiNode nodeH = { 'H', NULL, NULL };
//按图形成二叉树结构
nodeA.lchild = &nodeB;
nodeA.rchild = &nodeF;
nodeB.rchild = &nodeC;
nodeC.lchild = &nodeD;
nodeC.rchild = &nodeE;
nodeF.rchild = &nodeG;
nodeG.lchild = &nodeH;
//开始遍历
Foreach_Node(&nodeA);
return 0;
}
结果如下:
A B C D E F G H
改成中序遍历,只需要修改下Foreach_Node方法,将输出语句调整到中间
void Foreach_Node(struct BiNode *root)
{
if (NULL == root)
return;
//先左
Foreach_Node(root->lchild);
//再根
printf("%c ", root->ch);
//再右
Foreach_Node(root->rchild);
}
结果如下:
B D C E A F H G
改成后续遍历,只需要修改下Foreach_Node方法,将输出语句调整到末尾
void Foreach_Node(struct BiNode *root)
{
if (NULL == root)
return;
//先左
Foreach_Node(root->lchild);
//再右
Foreach_Node(root->rchild);
//再根
printf("%c ", root->ch);
}
结果如下:
D E C B H G F A
5.3 求二叉树的高度和叶子节点数目
1)求二叉树高度
高度是从叶子往根方向计算的,计算每个节点的高度的方式是左子树高度和右子树的最大值+1, 叶子的高度是1.因为没有左子树和右子树,也就是0+0+1=1, 如下图所示, 根节点A的高度就是4
代码实现:
void Get_Leaf_Count(struct BiNode *root, int* count)
{
if (NULL == root)
return;
if (root->lchild == NULL&& root->rchild == NULL)
{
(*count)++;
}
Get_Leaf_Count(root->lchild, count);
Get_Leaf_Count(root->rchild, count);
}
2)求叶子节点数
所谓叶子节点就是没有左子树和右子树的节点,如图所示叶子节点有3个
int Get_Tree_Height(struct BiNode *root)
{
if (NULL == root)
return 0;
int leftHeihgt = Get_Tree_Height(root->lchild);
int rightHeihgt = Get_Tree_Height(root->rchild);
return leftHeihgt > rightHeihgt ? leftHeihgt + 1 : rightHeihgt + 1;
}
5.4 二叉树的拷贝和内存释放
1)拷贝
拷贝每个节点也是遵循先拷贝左子树再拷贝右子树,最后拷贝节点的规律,和后续遍历规则一样,还是以前面的图为例,拷贝的代码如下
struct BiNode * Copy_Tree(struct BiNode *root)
{
if (NULL == root)
return NULL;
//先拷贝左子树
struct BiNode * lchild = Copy_Tree(root->lchild);
//再拷贝右子树
struct BiNode * rchild = Copy_Tree(root->rchild);
//创建结点,其实就是每个子树的节点
struct BiNode * new_node = (struct BiNode * )malloc(sizeof(struct BiNode));
new_node->lchild = lchild;
new_node->rchild = rchild;
new_node->ch = root->ch;
return new_node;
}
2)释放内存
释放的时候必须要从叶子往根的方向进行,否则根释放了,就无法找到下面的节点了,这个可以采用后续遍历的方式,也就是先左,再右,再根
void Free_Tree(struct BiNode *root)
{
if (NULL == root)
return ;
//先左
Free_Tree(root->lchild);
//再右
Free_Tree(root->rchild);
//再根
free(root);
}
5.5 二叉树的非递归遍历
大致步骤如下:
1)将根节点压栈
2)开始while循环,条件size>0
3)先从栈中弹出元素,判断元素的标记是否是第二次入栈,是的话就直接输出结果
4)如果是false则把当前节点的右子树和左子树压入栈,并且默认标识是false
5)然后在将当前节点的标识修改为true并入栈.
这里会用到栈的数据结构,可以使用顺序栈结构
#include "SeqStack.h"
#include<stdbool.h>
struct BiNode //树节点
{
char ch;
struct BiNode* lchild;//左子树
struct BiNode* rchild;//右子树
};
struct Info //作为栈存储元素的单元
{
struct BiNode *node;
bool flag;//true表示第二次入栈
};
struct Info* createInfo(struct BiNode *node, bool flag)
{
struct Info* info = malloc(sizeof(struct Info));
info->flag = flag;
info->node = node;
return info;
}
void Foreach_Tree(struct BiNode * root)
{
//创建栈结构
SeqStack stack = Init_SeqStack();
//将根节点入栈
Inseart_SeqStack(stack, createInfo(root, false));
while (Size_SeqStack(stack) > 0)
{
//获取栈顶元素
struct Info* top_element = (struct Info*) top_SeqStack(stack);
//弹出栈
pop_SeqStack(stack);
//判断标记
if (top_element->flag)
{
//输出标记是true的元素
printf("%c ", top_element->node->ch);
//释放出栈的元素的内存
free(top_element);
continue;
}
//如果是false则把当前节点的右子树和左子树压入栈,并且默认标识是false
if (top_element->node->rchild != NULL)
Inseart_SeqStack(stack, createInfo(top_element->node->rchild, false));
if (top_element->node->lchild != NULL)
Inseart_SeqStack(stack, createInfo(top_element->node->lchild, false));
//然后在将当前节点的标识修改为true并入栈.
top_element->flag = true;
Inseart_SeqStack(stack, top_element);
}
//销毁栈
Destory_SeqStack(stack);
}
int main()
{
//创建结点
struct BiNode nodeA = { 'A', NULL, NULL };
struct BiNode nodeB = { 'B', NULL, NULL };
struct BiNode nodeC = { 'C', NULL, NULL };
struct BiNode nodeD = { 'D', NULL, NULL };
struct BiNode nodeE = { 'E', NULL, NULL };
struct BiNode nodeF = { 'F', NULL, NULL };
struct BiNode nodeG = { 'G', NULL, NULL };
struct BiNode nodeH = { 'H', NULL, NULL };
//按图形成二叉树结构
nodeA.lchild = &nodeB;
nodeA.rchild = &nodeF;
nodeB.rchild = &nodeC;
nodeC.lchild = &nodeD;
nodeC.rchild = &nodeE;
nodeF.rchild = &nodeG;
nodeG.lchild = &nodeH;
//使用非递归遍历
Foreach_Tree(&nodeA);
system("pause");
}
六、插入排序
思路是将无序序列中的元素逐个插入到有序序列中
如何确定有序和无序序列?
先默认首元素就是有序的,后面的则表示无序的
假设按从小到大排序,那么每次都会从右边序列中取出一个元素和其左边所有元素挨个比较,如果比它小则将左侧序列的元素向右移动位置,空出位置,直到不满足条件的时候就在那个元素的后面插入该元素.同时右侧序列的指针后移一位.以此类推.
下面代码实现:
#include<stdio.h>
void Inseart_Sort(int *arr, int len)
{
for (int i = 1; i < len; ++i)
{
//i从1开始,表示默认先以0号元素为左侧序列
if (arr[i] < arr[i - 1])
{
//表示右侧元素比左侧大,需要移动位置
//先取出i位置的元素
int temp = arr[i];
//定好和左侧挨个比较的开始索引j
int j = i - 1;
//下面开始和左侧序列的元素逐个比较
for (; j >= 0 && temp < arr[j]; --j)
{
//开始逐个移动左侧元素
arr[j + 1] = arr[j];
}
//移动完后,第j个位置的元素是不符合条件的,那么就在它的后面插入temp
arr[j + 1] = temp;
}
}
}
int main()
{
int arr[] = { 3, 1, 2, 5, 9, 4, 7 };
int len = sizeof(arr) / sizeof(int);
Inseart_Sort(arr, len);
for (int i = 0; i < len; ++i)
{
printf("%d ,", arr[i]);
}
system("pause");
return 0;
}
结果如下:
1 ,2 ,3 ,4 ,5 ,7 ,9 ,