数据结构初学者必用:手把手教你写可复用代码模板(附完整示例)

初阶数据结构


目录

初阶数据结构

●1.顺序表

●2.链表

●3.栈

●4.队列

●5.二叉树


一、顺序表

1.顺序表代码模板 静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间 大小,所以下面我们实现动态顺序表。

  • SeqList.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
//静态顺序表
/*
#define N 10
typedef int SqLdatatype;
struct SeqList
{
	SqLdatatype arr[N];	//指定顺序表空间大小
	int size;                   //记录顺序表中元素个数
};
*/
 
//动态顺序表
typedef int SqLdatatype;
typedef struct SeqList
{
	SqLdatatype* arr;    //定义一个指向空间的指针,等待动态开辟顺序表空间
	int size;         //记录顺序表中元素个数
	int capacity;          //记录顺序表的容量大小
}SL;
 
void SLinit(SL* s);	//顺序表初始化
void SLdestory(SL* s);	//顺序表销毁
void checksizecapactiy(SL* s);	//检查顺序表的容量大小,容量满后继续动态扩容
void SLpushback(SL* s, sqldatatype x);	//尾插
void SLpopback(SL* s);	//尾删
void SLpushfront(SL* s, sqldatatype x);	//头插
void SLpopfront(SL* s);	//头删
int SLfind(SL* s, sqldatatype x);	//查找顺序表中指定元素所在的位置
void SLinsert(SL* s,int pos, sqldatatype x);		//往顺序表中指定位置进行插入
void SLerase(SL* s, int pos);	//删除顺序表中指定位置的元素
void SLmodify(SL* s, int pos, sqldatatype x);	//修改顺序表中指定位置的元素
  • SeqList.c文件
c++ 复制代码
#include "SeqList.h"
 
void SLinit(SL* s)
{
	s->arr = (SqLdatatype*)malloc(sizeof(SqLdatatype) * 10);	//开辟空间	
	s->size = 0;
	s->capacity = 10;
}
 
void SLdestory(SL* s)
{
	free(s->arr);	//释放空间
	s->arr = NULL;	//防止野指针,要将其指向空
	s->size = 0;
	s->capacity = 0;
}
 
void checksizecapactiy(SL* s)
{
	if (s->size == s->capacity)	//顺序表已满
	{
		SqLdatatype* tmp = (SqLdatatype*)realloc(s->arr, s->capacity * 2 * (sizeof(SqLdatatype)));	//动态扩容开辟,且以2倍进行扩容	
		s->arr = tmp;
		s->capacity *= 2;
	}
}
 
void SLpushback(SL* s, SqLdatatype x)
{
	checksizecapactiy(s);	//尾插前进行容量检查
	s->arr[s->size] = x;
	s->size++;
}
 
void SLpopback(SL* s)
{
	assert(s->size > 0);	//通过断言函数接口去判断边界非法情况
	s->size--;
}
 
void SLpushfront(SL* s, SqLdatatype x)
{
	checksizecapactiy(s);	//头插前进行容量检查
	for (int i = s->size - 1; i >= 0; i--)	//全部元素统一后移一位
	{
		int j = i + 1;
		s->arr[j] = s->arr[i];
	}
	s->arr[0] = x;
	s->size++;
}
 
void SLpopfront(SL* s)
{
	assert(s->size > 0);	//通过断言函数接口去判断边界非法情况
	for (int i = 1; i < s->size; i++)	//全部元素统一前移一位
	{
		int j = i - 1;
		s->arr[j] = s->arr[i];
	}
	s->size--;
}
 
int SLfind(SL* s, SqLdatatype x)
{
	for (int i = 0; i < s->size; i++)	//循环顺序表查找指定元素的位置
	{
		if (s->arr[i] == x)
		{
			return i;
		}
	}
}
 
void SLinsert(SL* s, int pos, SqLdatatype x)
{
	assert(pos>=0&&pos<=s->size);	//通过断言函数接口去判断边界非法情况
	checksizecapactiy(s);	//插入前进行容量检查
	for (int i = s->size - 1; i >= pos; i--)	//将插入指定位置后的元素全部后移一位
	{
		int j = i + 1;
		s->arr[j] = s->arr[i];
	}
	s->size++;
}
 
void SLerase(SL* s, int pos)
{
	for (int i = pos + 1; i < s->size; i++)	//将删除元素后面的元素全部向前移动一位
	{
		int j = i - 1;
		s->arr[j] = s->arr[i];
	}
	s->size--;
}
 
void SLmodify(SL* s, int pos, SqLdatatype x)
{
	assert(pos >= 0 && pos < s->size);	//通过断言函数接口去判断边界非法情况
	s->arr[pos] = x;
}

二、链表

1.单向链表代码模板(头结点+不循环) 带头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

  • SListNode.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
/*
typedef struct ElemType{
	//...
}ET;
*/
 
//单向链表
typedef int elemtype;
typedef struct SListNode{
	elemtype data;	//复合数据类型:ET data;
	struct SListNode *next;
}SLNode,*linklist;
 
SLNode* SLNode_Init();    //链表的初始化(创建头结点)
SLNode* SLNode_BuyListNode(elemtype x);    //动态申请一个结点
void SLNode_Print(SLNode *L);    //单链表打印
void SLNode_PushBack(SLNode *L,elemtype x);    //单链表尾插
void SLNode_PushFront(SLNode *L,elemtype x);    //单链表头插
void SLNode_PopBack(SLNode *L);    //单链表尾删
void SLNode_PopFront(SLNode *L);    //单链表头删
int SLNode_Find(SLNode *L,elemtype x);    //单链表查找
void SLNode_InsertPos(SLNode *L,elemtype x,int pos);    //单链表在pos位置之后插入x
void SLNode_DeletePos(SLNode *L,int pos);    //单链表删除pos位置之后的值
 
/*
void SLNode_Recycling(SLNode *L)    //使单链表循环化
{
	if(L==NULL)
	{
		perror("链表不存在");
		return;
	}
	if(L->next==NULL)
	{
		L->next=L;
		return;
	}
	SLNode*cur=L;
	while(cur->next!=NULL)
	{
		cur=cur->next;
	}
	cur->next=L;
}
*/
  • SListNode.c文件
c++ 复制代码
#include"SListNode.h"
 
SLNode* SLNode_Init() //链表必须先进行初始化操作,否则后续操作都会出现错误
{
    SLNode* newNode = (SLNode*)malloc(sizeof(SLNode));    //创建头结点,若无头结点的单向链表,后续操作将会涉及到双指针
    if (!newNode) {
        perror("链表初始化失败");
        return NULL;
    }
    newNode->next = NULL;
    return newNode;
}
 
SLNode* SLNode_BuyListNode(elemtype x)
{
	SLNode* newNode=(SLNode*)malloc(sizeof(SLNode));
	if(newNode==NULL){
		perror("链表初始化失败");
		return;
	}	
	newNode->data=x;	//将申请结点的数据直接放入结点的数据域中
	newNode->next=NULL;	
	return newNode;	//返回该节点进行后续的前后链接操作
}
 
void SLNode_Print(SLNode *L)
{
	if(L->next==NULL||L==NULL){
		perror("链表为空");
		return;
	}	//非法情况判断
	SLNode *cur=L->next;		//指向头结点的下一个数据结点
	while(cur!=NULL)	//遍历单向链表数据域
	{
		printf("%d",cur->data);
		cur=cur->next;
	}
	printf("\n");
}
 
void SLNode_PushBack(SLNode *L,elemtype x)
{
	SLNode *cur=L;
	while(cur->next!=NULL)
	{
		cur=cur->next;
	}
	SLNode* tmpNode=SLNode_BuyListNode(x);	//调用动态申请结点的函数接口,并返回一个临时节点
	cur->next=tmpNode; 	//尾指针结点的指针域指向该结点,使该结点成为尾结点
}
 
void SLNode_PushFront(SLNode *L,elemtype x)
{
	SLNode* tmpNode=SLNode_BuyListNode(x);	
	if(L->next==NULL)
	{
		L->next=tmpNode;
		return;
	}
	//将该结点的指针域指向此刻头结点的下一个结点,再将头结点的指针域指向该结点
	tmpNode->next = L->next;
    L->next = newNode;
}
 
void SLNode_PopBack(SLNode *L)
{
	if(L==NULL||L->next==NULL)
	{
		perror("链表为空");
		return 0;
	}	//非法情况判断
	SLNode *cur=L->next;		//遍历到链表的尾结点指针
	SLNode *pre=L;	//遍历到链表的倒数第二个结点指针
	if(cur->next!=NULL){
		pre=cur;
		cur=cur->next;
	}	//循环遍历,指针后移
	free(cur);		//释放cur指针所指向的尾结点,实现尾删
	cur=NULL;
	pre->next=NULL;	//上一个结点指针指向空,成为尾结点
}
 
void SLNode_PopFront(SLNode *L)
{
	ifL==NULL||L->next==NULL)
	{
		perror("链表为空");
		return 0;
	}	//非法情况判断
	SLNode *cur=L->next;		//指向待删除的第一个头数据结点
	L->next=cur->next;	//头结点指针指向头数据结点的下一个结点
	free(cur);		//释放cur指针所指向的头数据结点
	cur=NULL;
}
 
int SLNode_Find(SLNode *L,elemtype x)
{
	SLNode* cur=L->next;		//遍历结点的指针
	int cntNode=0;	//记录结点的个数
	while(cur!=NULL)
	{
		if(cur->data==x)	//遍历判断查找
		{
			return cntNode;
		}
		cur=cur->next;
		cntNode++;
	}
	return -1;
}
 
void SLNode_InsertPos(SLNode *L,elemtype x,int pos)
{
    if(pos<1)    //合法性判断
        return;
    SLNode *cnt_cur=L->next;
    int cnt=0;
    while(cnt_cur!=NULL)
    {
        cnt_cur=cnt_cur->next;
        cnt++;
    }
    if(pos>cnt)    //如果pos的值大于此刻链表中的结点个数,则直接默认插入到表尾
        pos=cnt;
 
	SLNode* tmpNode=SLNode_BuyListNode(x);
	if(L->next==NULL){
		L->next=tmpNode;
		return;
	}	//如果链表只有头结点,则直接进行插入
	SLNode* pre=L;
	SLNode* cur=L->next;
	for(int i=1;i<pos;i++)
	{
		pre=cur;
		cur=cur->next;
	}
	pre->next=tmpNode;
	tmpNode->next=cur;
}
 
void SLNode_DeletePos(SLNode *L,int pos)
{
    if(pos<1)    //合法性判断
        return;
    SLNode *cnt_cur=L->next;
    int cnt=0;
    while(cnt_cur!=NULL)
    {
        cnt_cur=cnt_cur->next;
        cnt++;
    }
    if(pos>cnt)    //如果pos的值大于此刻链表中的结点个数,则直接默认删除表尾结点
        pos=cnt;
 
	SLNode* pre=L;
	SLNode* cur=L->next;
	for(int i=1;i<pos;i++)
	{
		pre=cur;
		cur=cur->next;
	}
	pre->next=cur->next;
	free(cur);
	cur=NULL;
}

2.双向链表代码模板(头结点+循环) 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。

  • DbeList.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
typedef int Dbedatatype;
typedef struct Dbelist {
	Dbedatatype data;		//结点数据域
	struct Dbelist* next;	//后指针
	struct Dbelist* prev;	//前指针
}LTNode;
 
LTNode* buylistnode(Dbedatatype x);	//创建一个数据域为x的新结点
LTNode* LTlist();	//双向链表的初始化
void LTprint(LTNode* phead);	//遍历整个双向链表
void LTpushback(LTNode *phead,Dbedatatype x);	//尾插
void LTpopback(LTNode* phead);	//尾删
void LTpushfront(LTNode* phead, Dbedatatype x);	//头插
void LTpopfront(LTNode* phead);	//头删
int LTsize(LTNode* phead);	//双向链表的长度
LTNode* LTfind(LTNode* phead, Dbedatatype x);	//查找双向链表中元素值为x的结点
void LTinsertpos(LTNode* pos, Dbedatatype x);	//在双向链表中的指定pos结点位置插入结点
void LTerasepos(LTNode* pos);	//删除双向链表指定pos结点位置的结点
void LTdestory(LTNode* phead);		//双向链表的销毁
  • DbeList.c文件
c++ 复制代码
#include "DbeList.h"
 
LTNode* buylistnode(Dbedatatype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}	//创建结点后进行非法情况判断
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
 
LTNode* LTlist()
{
	LTNode* phead = buylistnode();	//初始化头结点
	phead->next = phead;		
	phead->prev = phead;
	return phead;
}
 
void LTprint(LTNode* phead)
{
	assert(phead);	//通过断言函数接口进行非法情况判断
	printf("phead");	
	LTNode* cur = phead->next;
	while (cur)	//遍历输出双向链表中的数据元素
	{
		printf("<->%d", cur->data);
		cur = cur->next;
	}
}
 
void LTpushback(LTNode* phead, Dbedatatype x)
{
	assert(phead);	//通过断言函数接口进行非法情况判断
	//创建指向新结点的指针和指向尾结点的指针
	LTNode* newnode = buylistnode(x);	
	LTNode* tail = phead->prev;
	//进行尾插,即前后指针域的链接
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
 
void LTpopback(LTNode* phead)
{
	assert(phead);	
	assert(phead->next!=phead);	//通过断言函数接口进行非法情况判断
	//创建三个结点指针将其分别指向尾结点、尾结点的前一个结点及头结点
	LTNode* tail = phead->prev;
	LTNode* newtail = tail->prev;
	LTNode* cur = phead;
	//进行尾删及新链表前后指针域的链接
	free(tail);
	tail = NULL;
	newtail->next = cur;
	cur->prev = newtail;
}
 
void LTpushfront(LTNode* phead, Dbedatatype x)
{
	assert(phead);	//通过断言函数接口进行非法情况判断
	//创建指向新结点及头数据结点的指针
	LTNode* newnode = buylistnode(x);
	LTNode* headnext = phead->next;
	//进行头插,即前后指针域的链接
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = headnext;
	headnext->prev = newnode;
}
 
void LTpopfront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//通过断言函数接口进行非法情况判断
	//创建指向头数据结点的指针
	LTNode* headnext=phead->next;
	//进行头删及新链表前后指针域的链接
	headnext->prev=phead;
	phead->next=headnext->next;
	free(headnext);
	headnext=NULL;
}
 
int LTsize(LTNode* phead)
{
	assert(phead);//通过断言函数接口进行非法情况判断
	//创建指向头数据结点的指针及计数器
	LTNode* cur = phead->next;
	int cnt = 0;
	while (cur != phead)	//遍历双向链表进行数据结点计数
	{
		cnt++;
		cur = cur->next;
	}
	return cnt;
}
 
LTNode* LTfind(LTNode* phead, Dbedatatype x)
{
	assert(phead);	//通过断言函数接口进行非法情况判断
	LTNode* cur = phead->next;	//创建指向头数据结点的指针
	//遍历双向链表找出数据域值为x的结点并返回
	while (cur)
	{
		if(cur->data==x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
 
void LTinsertpos(LTNode* pos, Dbedatatype x)
{
	assert(pos);	//通过断言函数接口进行非法情况判断
	//创建指向pos结点前一个结点的指针及新结点的指针
	LTNode* posprev = pos->prev;
	LTNode* newnode = buylistnode(x);
	//进行前后指针数据域的链接
	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;
}
 
void LTerasepos(LTNode* pos)
{
	assert(pos);//通过断言函数接口进行非法情况判断
	//创建指向pos结点前一个结点的指针及后一个结点的指针
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	//删除pos结点并进行前后指针域的链接
	free(pos); 
	pos = NULL;
	posprev->next = posnext;
	posnext->prev = posprev;
}
 
void LTdestory(LTNode* phead)
{
	assert(phead);	//通过断言函数接口进行非法情况判断
	LTNode* cur = phead->next;	
	while (cur)	//遍历双向链表进行结点的删除
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}	
	//最终释放头结点并将其指向NULL
	free(phead);
	phead = NULL;	
}

三、栈

1.栈代码模板 栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

  • Stack.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
typedef int STtypedata;
typedef struct stack {
	STtypedata* a;
	int top;	//栈顶
	int capacity;	//容量
}ST;
 
void STinit(ST *ps);		//栈的初始化
void STdestory(ST *ps);		//栈的销毁
void STpush(ST *ps,STtypedata x);	//入栈
void STpop(ST* ps);	//出栈
STtypedata STtop(ST* ps);	//取得栈顶元素
int STsize(ST* ps);		//栈中元素数量
bool STempty(ST* ps);		//判断栈是否为空
  • Stack.c文件
c++ 复制代码
#include "stack.h"
 
void STinit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity=0;
	ps->top = 0;
}
 
void STdestory(ST* ps)
{
	assert(ps);
	free(ps->a);	//释放栈空间
	ps->a = NULL;	//将栈指针赋为空
	ps->capacity = ps->top = 0;
}
 
void STpush(ST* ps, STtypedata x)
{
	assert(ps);
	if (ps->top == ps->capacity)	//判断是否栈满
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STtypedata* tmp = (STtypedata*)realloc(ps->a, sizeof(STtypedata) * newcapacity);	//扩容
		if (tmp == NULL)
		{
			perror("relloc fail");
			exit(-1);
		}
		ps->a = tmp;	//将栈指针指向扩容空间首地址
		ps->capacity = newcapacity;	//修改容量为新容量大小
	}
	ps->a[ps->top] = x;	//入栈
	ps->top++;	//栈顶计数++
}
 
void STpop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;	//栈顶计数--,出栈
}
 
STtypedata STtop(ST* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];	//返回栈顶元素
}
 
int STsize(ST* ps)
{
	assert(ps);
	return ps->top;	//返回栈顶长度,即元素个数
}
 
bool STempty(ST* ps)
{
	assert(ps);
	if (ps->top == 0)	//判断栈是否为空
	{
		return true;
	}
	return false;
}

四、队列

1.队列代码模板 队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

  • Queue.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
/*
typedef struct binarytreenode{
	struct binarytreenode *Lchild;
	struct binarytreenode *Rchild;
	elemtype data;
}btnode;
typedef  btnode* qtypedata;  //二叉树层序遍历,对队列的简单改造
*/
 
typedef int qtypedata; 
typedef struct queuenode {
	struct queuenode* next;
	qtypedata a;
}qnode;	//队列中的结点
typedef struct queue {
	qnode* head;		//队头指针
	qnode* tail;		//队尾指针
	int size;		//队列长度
}que;	
 
void queueinit(que *ps);	//队列的初始化
void queuepush(que* ps,qtypedata x);	//入队
void queuepop(que* ps);	//出队
void queuedestory(que* ps);	//队列的销毁
qtypedata queuehead(que* ps);	//拿到队头元素
qtypedata queuetail(que* ps);	//拿到队尾元素
bool queueempty(que* ps);		//判断队列是否为空
int queuesize(que* ps);		//队列的长度
  • Queue.c文件
c++ 复制代码
#include "queue.h"
 
void queueinit(que* ps)
{
	assert(ps);
	ps->head = NULL;
	ps->tail = NULL;
	ps->size = 0;
}
 
void queuepush(que* ps, qtypedata x)
{
	assert(ps);
 
	qnode* newnode = (qnode*)malloc(sizeof(qnode));		//入队,创建一个新结点
	if (newnode == NULL){
		perror("malloc fail");
		exit(-1);
	}	
	newnode->a = x;	//将x赋值入结点的数据域
	newnode->next = NULL;	//结点指针域指向空作为尾结点,队列为尾插
 
	if (ps->tail == NULL)	//判断是否为第一个结点,若是直接头尾指针指向,反之进行尾插链接
	{
		ps->head=ps->tail= newnode;
	}
	else
	{
		ps->tail->next = newnode;
		ps->tail = newnode;
	}
	ps->size++;	//队列长度++
}
 
void queuepop(que* ps)
{
	assert(ps);
	assert(!queueempty(ps));
	if (ps->head->next==NULL)	//判断是否为第一个结点,若是直接释放并指向空,否则进行头出链接
	{
		free(ps->head);
		ps->head = ps->tail = NULL;
	}
	else
	{
		qnode* del = ps->head->next;
		free(ps->head);
		ps->head = del;
	}
	ps->size--;	//队列长度--
 
}
 
void queuedestory(que* ps)
{
	assert(ps);
 
	qnode* cur1 = ps->head;
	while (cur1)	//遍历队列链表进行队列的销毁
	{
		qnode* cur2 = cur1->next;
		free(cur1);
		cur1 = cur2;
	}
	ps->head = ps->tail = NULL;	//头尾指针指向空
	ps->size = 0;		//队列长度为0
}
 
qtypedata queuehead(que* ps)
{
	assert(ps);
	assert(!queueempty(ps));
	return ps->head->a;	//返回队头元素
}
 
qtypedata queuetail(que* ps)
{
	assert(ps);
	return ps->tail->a;	//返回队尾元素
}
 
bool queueempty(que* ps)	
{
	assert(ps);
	return ps->head == NULL;	//判断队头指针是否为空,若是空返回0,反之返回1
}
 
int queuesize(que* ps)
{
	assert(ps);
	return ps->size;	//返回队列长度
}

五、二叉树

1.二叉树代码模板 二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 ,链式结构又分为二叉链和三叉链。

  • Binarytree.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
typedef int btdatatype;
typedef struct binarytreenode {
	btdatatype val;	//数据域
	struct binarytreenode* leftchild;		//左孩子指针
	struct binarytreenode* rightchild;	//右孩子指针
}btnode;
 
btnode* buynewnode(btdatatype x);		//创建一个新结点
void pretraversal(btnode *root);		//前序遍历
void intraversal(btnode* root);	//中序遍历
void aftertraversal(btnode* root);	//后序遍历
int treesize(btnode *root);		//树中的结点个数
int treeleafsize(btnode* root);	//树中的叶子节点个数
int treeklevel(btnode* root,int level);	//树第level层结点总数
void treedestory(btnode* root);		//树的销毁
btnode* treefind(btnode* root,int x);	//查找树中的结点
void levelorder(btnode *root);		//层序遍历
bool treecomplete(btnode* root);	//判断是否为完全二叉树
int treehigh(btnode* root);		//树的高度
  • Binarytree.c文件
c++ 复制代码
#include "binarytree.h"
#include"queue.h"	//引入队列数据结构的头文件进行后续层序遍历的操作
 
btnode* buynewnode(btdatatype x)
{
	btnode* node = (btnode*)malloc(sizeof(btnode));	//开辟一个结点
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	node->val = x;	//将x值赋入二叉树结点的数据域
	node->leftchild = NULL;	//左孩子指空
	node->rightchild = NULL;	//右孩子指空
	return node;
}
 
void pretraversal(btnode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		printf("%d ", root->val);	//前序遍历
		pretraversal(root->leftchild);	//先左
		pretraversal(root->rightchild);	//后右
	}
}
void intraversal(btnode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		pretraversal(root->leftchild);	//先左
		printf("%d ", root->val);	//中序遍历
		pretraversal(root->rightchild);	//后右
	}
}
void aftertraversal(btnode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	else
	{
		pretraversal(root->leftchild);	//先左
		pretraversal(root->rightchild);	//后右
		printf("%d ", root->val);	//后序遍历
	}
}
 
int treesize(btnode* root)
{
	if (root == NULL){
		return 0;
	}
	else{
		return treesize(root->leftchild) + treesize(root->rightchild) + 1;	//递归进行结点计数
	}
}
 
int treeleafsize(btnode* root)
{
	if (root == NULL){
		return 0;
	}
	else if(root->leftchild!=NULL||root->rightchild!=NULL){
		return treeleafsize(root->leftchild) + treeleafsize(root->rightchild) ;	//递归进行叶子结点计数
	}
	else {
		return 1;
	}
}
 
int treeklevel(btnode* root, int level)
{
	assert(level> 0);
	if (root == NULL) {
		return 0;
	}
	else if(level==1){
		return 1;
	}
	else {
		return treeklevel(root->leftchild,level-1) + treeklevel(root->rightchild,level-1);//递归进行level层结点计数
	}
}
 
void treedestory(btnode* root)
{
	if (root == NULL)
	{
		return;
	}
 
	treedestory(root->leftchild);
	treedestory(root->rightchild);
	free(root);	//通过递归进行树的释放销毁
}
 
btnode* treefind(btnode* root, int x)
{
	if (root == NULL)
		return NULL;
 
	if (root->val == x)	//如果找到该节点,则直接返回
		return root;
 
	btnode* p=treefind(root->leftchild,x);	//左递归查找
	if (p != NULL)
	{
		return p;
	}
	btnode *q=treefind(root->rightchild,x);	//右递归查找
	if (q != NULL)
	{
		return q;
	}
	return NULL;
}
 
void levelorder(btnode* root)
{
	que q;	//定义队列
	queueinit(&q);	//队列的初始化
	if (root)
		queuepush(&q,root);
	while (!queueempty(&q))	//以队列为容器进行层序遍历,每次判断是否为空
	{
		btnode* front = queuehead(&q);
		printf("%d ", front->val);	//输出此刻队头结点的数据域
		if (front->leftchild)	//判断是否有左孩子,有的话入队
		{
			queuepush(&q,front->leftchild);
		}
		if (front->rightchild)	//判断是否有右孩子,有的话入队
		{
			queuepush(&q,front->rightchild);
		}
		queuepop(&q);	//队头出队
	}
	printf("\n");
	queuedestory(&q);
}	
 
bool treecomplete(btnode* root)
{
	que q;
	queueinit(&q);
 
	if (root)
		queuepush(&q, root);
	while (!queueempty(&q))
	{
		btnode* front = queuehead(&q);
		if (front == NULL)
			break;
		queuepush(&q,front->leftchild);
		queuepush(&q,front->rightchild);
		queuepop(&q);
	}
 
	while (!queueempty(&q))
	{
		btnode* front = queuehead(&q);
		queuepop(&q);
		if (front!=NULL)
		{
			queuedestory(&q);
			return false;
		}
	}
	queuedestory(&q);
	return true;
}
 
int search_max_num(int num1, int num2)
{
	return num1 > num2 ? num1 : num2;		//返回最大值
}
int treehigh(btnode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int num_left = treehigh(root->leftchild);
	int num_right = treehigh(root->rightchild);
	
	return search_max_num(num_left, num_right) + 1;
}

2.堆代码模板 普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

  • Heap.h文件
c++ 复制代码
#pragma once
//#define _CRT_SECURE_NO_WARNINGS -1	//该行代码需要注意自己编译器环境配置要求
 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<string.h>
#include<time.h>
#include<math.h>
 
typedef int HPdatatype;
typedef struct Heap {
	HPdatatype* a;	//堆空间指针
	int size;		//堆的大小
	int capcapacity;	//堆的容量
}HP;
 
void Heapinit(HP *php);	//堆的初始化
void Heapprint(HP* php);	//堆的输出
void Heapdestory(HP* php);	//堆的销毁
bool Heapempty(HP* php);		//堆的判空
void Heappush(HP* php, HPdatatype x);	//入堆
void Heappop(HP* php);	//出堆
HPdatatype Heaptop(HP* php);		//拿到对顶元素
void small_top_pile(HPdatatype* a, HPdatatype child);	//构建小顶堆
void big_top_pile(HPdatatype* a, HPdatatype child);	//构建大顶堆
void swap(HPdatatype* child, HPdatatype* father);		//元素交换
  • Heap.c文件
c++ 复制代码
#include "heap.h"
 
void Heapinit(HP* php)
{
	assert(php->a);
	php->a = NULL;	
	php->size = 0;
	php->capcapacity = 0;
}
 
void Heapprint(HP* php)
{
	assert(php);
	for (size_t i = 0; i < php->size; i++)	//遍历输出堆中的所有元素
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}
 
void Heapdestory(HP* php)
{
	assert(php);
 
	free(php->a);		//释放堆空间
	php->a = NULL;
	php->size = 0;
	php->capcapacity = 0;
}
 
bool Heapempty(HP* php)
{
	assert(php);
	if (php->size == 0) {	//判断堆是否为空
		return false;
	}
	else {
		return true;
	}
}
 
void swap(HPdatatype* child, HPdatatype* father)
{
	int tmp = *child;
	*child = *father;
	*father = tmp;
}
 
// 生成小顶堆
void small_top_pile(HPdatatype *a, HPdatatype child)
{
	int parent = (child - 1) / 2;
	while (child > 0)	//当孩子结点没有调到根节点时进行循环
	{
		if (a[child] < a[parent]) {	//判断孩子结点是否比父节点小,若是则进行交换,反之退出循环
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}
}
 
//生成大顶堆
void big_top_pile(HPdatatype* a, HPdatatype child)
{
	int parent = (child - 1) / 2;
	while (child > 0)//当孩子结点没有调到根节点时进行循环
	{
		if (a[child] > a[parent]) {	//判断孩子结点是否比父节点大,若是则进行交换,反之退出循环
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else {
			break;
		}
	}
}
void Heappush(HP* php, HPdatatype x)
{
	assert(php);
 
	if (php->size == php->capcapacity)	//判断堆空间是否已满,若满则需要进行扩容
	{
		int newcapcapacity=0;
		if (php->capcapacity == 0) {
			php->capcapacity = 4;
		}
		else {
			newcapcapacity = php->capcapacity * 2;
		}
		HPdatatype* tmp = (HPdatatype*)realloc( php->a,sizeof(HPdatatype)*newcapcapacity);	//空间扩容
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capcapacity = newcapcapacity;
	}
 
	php->a[php->size] = x;	//进行入堆
	php->size++;		//堆的长度++
	small_top_pile(php->a,php->size-1);	//默认构建的是小顶堆
	//big_top_pile(php->a, php->size - 1);
}
 
// 对于小堆来说,需要进行的操作;
void adjustdown(HPdatatype *a,int size,int root)	//下沉
{
	int child = root * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] > a[child])
		{
			++child;
		}
 
		if (a[child] > a[root])
		{
			swap(&a[child], &a[root]);
			root = child;
			child = root * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
 
//对于大堆来说,需要进行的操作;
void adjustup(HPdatatype* a, int size, int root)		//上浮
{
	int child = root * 2 + 1;
	while (child<size)
	{
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[child] < a[root])
		{
			swap(&a[child], &a[root]);
			root = child;
			child = 2 * root + 1;
		}
		else
		{
			break;
		}
	}
}
 
void Heappop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	swap(&php->a[0], &php->a[php->size - 1]);	//交换堆顶和堆尾元素
	php->size--;	//堆长度--
	adjustup(php->a, php->size, 0);		//此刻堆尾元素为出堆元素,进行上浮对堆进行新的调整
	//adjustdown(php->a, php->size, 0);
}
 
HPdatatype Heaptop(HP* php)
{
	assert(php);
	assert(php->size > 0);
 
	HPdatatype ret= php->a[0];	//拿到堆顶元素
	return ret;
}

<您的三连和关注是我最大的动力>
🚀 文章作者:张同学的IT技术日记
分类专栏:数据结构

相关推荐
空白到白1 小时前
算法练习-合并两个有序数组
数据结构·python·算法
花开富贵ii3 小时前
代码随想录算法训练营四十九天|图论part07
java·数据结构·算法·图论·prim·kruscal
Forest235 小时前
浅谈ArrayList的扩容机制
java·数据结构
XMZH030426 小时前
数据结构:链式队列尝试;0826
数据结构·链表·队列·链式队列
振鹏Dong6 小时前
Redis核心机制解析:数据结构、线程模型与内存管理策略
数据结构·数据库·redis
7hhhhhhh13 小时前
自学嵌入式第二十六天:数据结构-哈希表、内核链表
数据结构·链表·散列表
3壹16 小时前
单链表:数据结构中的高效指针艺术
c语言·开发语言·数据结构
耳总是一颗苹果19 小时前
排序---插入排序
数据结构·算法·排序算法
YLCHUP19 小时前
【联通分量】题解:P13823 「Diligent-OI R2 C」所谓伊人_连通分量_最短路_01bfs_图论_C++算法竞赛
c语言·数据结构·c++·算法·图论·广度优先·图搜索算法