数据结构— C/C++(期末速通纯代码版)

该博客围绕数据结构展开,介绍了常见的时间复杂度,详细阐述线性表(顺序表、链表)、栈、队列、树(二叉树)的定义、操作及实现,还讲解了查找方法,如顺序查找、带监视哨的顺序查找、拆半查找和插值查找等信息技术相关内容。

author:Vmu
finish date:2023-12-22


前言

本文是以C语言作为示范语言的数据结构纯代码版
以下是本篇文章正文内容,下面案例可供参考

一、常见的时间复杂度

在这里插入图片描述

二、线性表

线性表分为两种不同的存储数据的方式,顺序表(数组)和链表

顺序表基本操作和功能实现

特点:逻辑地址相邻 物理地址也相邻

1.线性表的静态定义:

#define MaxSize 100;		//定义顺序表的最大长度
typedef struct{
	ElemType data[MaxSize];
	int length;				//当前顺序表的长度
}SqlList;					//顺序表的类型定义

2.线性表的静态初始化:

void InitList(SqlList &L){
	for(int i=0;i<MaxSize;i++)
		L.data[i] = 0;		//防止“脏数据”
	L.length = 0;
}

3.线性表的插入:

bool ListInsert(SqlList &L,int i,ElemType e){
	if(i < 1 || i > L.length)		//判断i值是否合法
		return false;
	if(L.length > L.MaxSize)		//判断
		return false;
	for(int j=L.length;j>=i;j--)
		L.data[j] = L.data[j-1];
	L.data[i-1] = e;
	L.length++;
	return true;
}

4.线性表的删除:

bool ListDelete(SqlList &L,int i,ElemType &e){
	if(i < 1 || i > L.length)
		return false;
	e = L.data[i-1];
	for(int j=i;j<L.length;j++)
		L.data[j-1] = L.data[j];
		L.length--;
		return true;
}

5.线性表的按索引查找:

ElemType GetElem(SqlList L,int i){
	return L.data[i-1];
}

6.线性表的按值查找:

ElemType LocateElem(SqlList L,ElemType e){
	for(int i=0;i < L.length;i++)
		if(L.data[i] == e)
			return i+1;
	return 0;
}

7.动态增长内存:

void IncreaseSize(SqlList &L,int len){
	int *p = L.data;
	L.data = (int *)malloc((L.MaxSize+len) * sizeof(int));
	for(int i=0;i < L.length;i++)
		L.data[i] = p[i];
	L.MaxSize = L.MaxSize + len;
	free(p);
}

链表

特点:逻辑地址上相邻吗,物理上不一定相邻,由指针指向下一个数据元素
链表分为单向链表 和 双向链表

单向链表

1.单项链表的定义:
typedef struct node_t{	//把结构体类型冲重定义为LinkNode_t
	int data;
	struct node_t *next;
}LinkNode_t

//功能:创建一个链表的头结点(堆空间)
//返回值:头结点的地址
//为简化程序,本程序没有做申请空间失败的检查
LinkNode_t *createLinkList()	
{
	LinkNode_t* node = (LinkNode_t*)malloc(sizeof(LinkNode_t));
	node->next = NULL;	//最后一个结点的next一定指向NULL
	return node;		//返回指向头结点的指针
}

下面我们调用这个函数时:

LinkNode_t* head = createLinkList();

此时便创建了一个新的单向链表,函数返回的结点指针是这个链表的头结点,这个结点我们不存储数据
此时链表中只有一个结点即head

2.判断链表是否为空
//参数:链表头结点指针
//返回值:空返回1 非空返回0
int emptyLinkList(LinkNode_t* p)
{
	return p->next == NULL;
}
3.计算链表中除空头结点外的结点个数
//参数:链表头结点指针
//返回值:结点个数
int lengthLinkList(LinkNode_t* p)
{
	int count = 0;
	LinkNode_t* t = p->next;	//不能直接用head作为判断条件
	while(t!=NULL)				//不要轻易动头结点head
	{
		count++;
		t = t->next;
	}
	return count;
}
4.插入新结点

找到带插入位置的前一个结点的地址,插入结点时,先使带插入结点的指针域指向后一个结点,再使前一个结点的指针域指向带插入的结点

/*
参数1:链表头结点指针
参数2:插入的位置(第一个有数据的结点位置为0)
参数3:新结点的值
返回值:成功返回0 失败返回-1
*/
int insertLinkList(LinkNode_t* p int pos,int x)
{
	if(pos<0 || pos>lengthLinkList(p))//对pos合法性检查
	{
		return -1;
	}
	//构建一个新的结点,并赋值
	LinkNode_t* newNode = (LinkNode_t*)malloc(sizeof(LinkNode_t));
	newNode->data = x;		//赋值
	newNode->next = NULL; 	//暂时指向空
	
	LinkNode_t* t=p;		//指向空头结点,从空头结点开始遍历
	int i;
	for(i=0;i<pos;i++)
	{
		t = t->next;
	}
	//将结点插入链表
	newNode->next = t->next;
	t->next = newNode;
	return 0;
}
5.获得某位置结点的值
/*
参数1:链表头结点指针
参数2:位置(第一个有数据的结点位置为0)
返回值:失败返回-1 成功返回参数2位置结点的值
*/
int getLinkList(LinkNode_t* p,int pos)
{
	if(pos<0 || pos>lengthLinkList(p))
	{
		return -1;
	}
	int i;
	LinkNode_t* t = p->next;
	for(i=0;i<pos;i++)	//遍历到pos位置
	{
		t = t->next;
	}
	return t->data;
}
6.遍历输出链表中所有结点的值
/*
参数:链表头结点指针
返回值:无
*/
void printLinkList(LinkNode_t* p)
{
	printf("\n有效数据节点数:%d\n",lengthLinkList(p));
	int count = 0;
	LinkNode_t* t = p->next;
	while(t!=NULL)
	{
		printf("NODE[%d]:%d\n",count,t->data);
		t = t->next;
		count++;
	}
}
7.删除链表中的结点

要删除这个结点只需要它前一个结点的指针指向要删除的节点的下一个节点,然后释放这段内存

/*
参数1:链表头结点指针
参数2:删除位置
返回值:失败返回-1 成功返回0
*/
int deleteLinkList(LinkNode_t* p,int pos)
{
	if(pos<0 || pos>=lengthLinkList(p))
	{
		return -1;
	}
	int i;
	LinkNode_t* t = p;//指向空头
	for(i=0;i<pos;i++)//循环结束得到待删除结点的前一个结点的位置
	{
		t = t->next;
	}
	LinkNode_t* s = t->next;
	t->next = t->next->next;
	free(s);
	return 0;
}
8.清空链表
//参数:链表头结点指针
void clearLinkList(LinkNode_t* p)
{
	while(p!=NULL)
	{
		LinkNode_t* t = p;
		p = p->next;
		free(t);
	}
}
测试
int main()
{
	LinkNode_t* head = createLinkList();
	insertLinkList(head,0,10);	//10
	insertLinkList(head,0,20);	//20 10
	printLinkList(head);
	printf("\n%d\n",getLinkList(head,0));//20
	deleteLinkList(head,0);
	printLinkList(head);
	return 0;
}

双向链表(不是重点但是习题得会)

不作为重点考核所以只保留可能或考到的部分

1.双向链表的定义:
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;
	LTDataType Data;
	struct ListNode* next;
}ListNode;
2.双链表的初始化:
//双向链表的初始化 不用二级指针 利用返回值
ListNode* listNode=ListInit();
ListNode* ListInit()
{
	//创造哨兵位的头节点
	ListNode* guard = (ListNode*)malloc(sizeof(ListNode));
	if(guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}
3.双链表在pos之前插入:
void ListInsert(ListNode* pos,LTDataType x)
{
	assert(pos);
	//记录pos的前一个
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
	newnode->prev = prev;
}
4.学习通的习题

在这里插入图片描述
第一空:
s->prior=p->prior

第二空:
p->prior->next=s

第三空:
s->next=p

第四空:
p->prior=s

三、栈

1.栈的定义

typedef struct{
	SDataType *base; 	//栈底指针
	SDataType *top;		//栈顶指针
	int StackSize;		//当前已经分配的存储空间,以元素为单位
}SqStack;

2.初始化栈

//初始化顺序栈,构造一个空栈
Status InitStack(SqStack &S){
	//分配存储空间
	s.base = (SDataType *)malloc(STACK_INIT_SIZE*sizeof(SDataType));
	if(!S.base){
		//如果分配失败,则返回error
		return OVERFLOW;
	}
	//S.top 始终指向栈顶元素的下一个位置
	S.top = S.base;					//初始状态下为空栈
	S.StackSize = STACK_INIT_SIZE;	//当前已经分配的存储容量为100个
	return OK;

3.判断是否为空栈

void judgeNull(SqStack &s){
	if(s.top == s.base){
		printf("此栈为空栈!\n");
	}else{
		printf("此栈不为空栈!\n");
	}
}

4.判断是否为满栈

void judgeNull(SqStack &s){
	if(s.top-s.base == s.StackSize){
		printf("栈满!\n");
	}else{
		printf("栈未满!\n");
	}
}

5.入栈

Status Push(SqStack &s,SDataType e){
	SDataType *p;
	//首先判断栈是不是满的(上溢)
	if(s.top-s.base == s.StackSize){
		//追加空间
		p = (SDataType *)realloc(s.base,(s.StackSize + STACKINCREMENT)*sizeof(SDataType));
		if(!p){
		//如果没有找到符合条件的存储空间,则返回error
			return OVERFLOW;
		}
		//成功找到则使s.base指向p
		s.base = p;
		s.top = s.base + s.StackSize;
	}
	//先插入元素,然后将栈顶指针加 1
	*(s.top) = e;
	s.top++;·
	return OK;
}

6.出栈

Status Pop(SqStack &s,SDataType &e){
	//判断是否会发生下溢
	if(s.top != s.base){
		s.top--;
		e = *(s.top);
	}else{
		return 0;
	}
	return 0;
}

链栈(不一定是重点)

//入栈
int push(struct link *top,int x){
	struct link *p;
	p=(struct link *)malloc(sizeof(struct link));
	if(!p)
		return ERROR;
	p->data=x;
	p->next=top->next;
	top->next =p;
	return OK;
} 
//出栈
int pop(struct link *top,int *x){
	struct link *p;
	p=top->next ;
	if(p==NULL){
		printf("栈为空\n");
	return OVERFLOW;
	}
	top->next =p->next ;
	*x=p->data ;
	free(p);
	return OK;
} 

整体代码

#include<stdio.h>
#include<malloc.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -2 
//链栈表示 
struct link{
int data;
 struct link *next;
};
//链栈初始化
void initstack(struct link *top){
 top=(struct link *)malloc(sizeof(struct link));
 top->next =NULL;
} 
//入栈
int push(struct link *top,int x){
 struct link *p;
 p=(struct link *)malloc(sizeof(struct link));
 if(!p)
  return ERROR;
 p->data=x;
 p->next=top->next;
 top->next =p;
 return OK;
} 
//出栈
int pop(struct link *top,int *x){
 struct link *p;
 p=top->next ;
 if(p==NULL){
  printf("栈为空\n");
  return OVERFLOW;
 }
 top->next =p->next ;
 *x=p->data ;
 free(p);
 return OK;
} 
int main(){
 struct link top;
 initstack(&top);
 int a;
 int i,j;
 for(i=5;i>0;i--){
  push(&top,i);
  printf("入栈元素为:%d\n",i);
 }
 for(j=5;j>0;j--){
  pop(&top,&a);
  printf("出栈元素为:%d\n",a);
 }
 return 0;
}

四、队列

队列—顺序存储

1.定义顺序队列存储结构

#define MaxSize 256

typedef struct
{
	int data[MaxSize];	//存放队列元素
	int front,rear;		//队头指针和队尾指针
}SqQueue;

2.初始化

int InitQueue(SqQueue* Q)
{
	Q->front = 0;
	Q->rear = 0;
	return 1;
}
SqQueue Q;
InitQueue(&Q);

3.求顺序表长度

int QueueLength(SqQueue Q)
{
	return Q.rear - Q.front;
}

4.入队

int EnQueue(SqQueue* Q,int e)
{
	if((Q->rear+1)%MaxSize == Q->front)	//判断队列是否满
		return 0;
	Q->data[Q->rear] = e;	//元素e赋值给队尾
	Q->rear++;				//队尾指针后移
	return 1;
}

4.出队

int DeQueue(SqQueue* Q,int* e)
{
	if(Q->rear == Q->front)	//队空
		return 0;
	*e = Q->data[Q->front];	//队头元素赋值给e
	Q->front++;				//队头指针后移
	return 1;
}

队列—链式存储(链队)

1.数据结点类型DataNode

typedef int ElemType;

//值节点--多个
typedef struct Node
{
	ElemType data;		//存储队列的元素值
	struct Node *next;	//存储下一个元素节点的地址
}DataNode

2.链队头结点类型

//头结点
typedef struct
{
	DataNode *front;//存储队头元素节点的地址(队首指针)
	DataNode *rear;	//存储队尾元素节点的地址(队尾指针)
}LinkQueue;

3.链队初始化

//构造一个空的队列,即创建一个链队结点,将front和rear域都设置为NULL
LinkQueue *InitQueue()
{
	LinkQueue *q;
	q = (LinkQueue *)malloc(sizeof(LinkQueue));
	q->front=NULL;
	q->rear=NULL;
	return q;
}
LinkQueue lq = *InitQueue();

4.入队

void enQueue(LinkQueue *q,ElemType e)
{
	DataNode *t;
	//1.构建一个节点t,data域存储e,next域存储NULL
	t = (DataNode *)malloc(sizeof(DataNode));
	t->data=e;
	t->next=NULL;
	//2.添加
	if(q->front!=NULL||q->rear!=NULL)//非空
	{//队非空
		q->rear->next=t;
		q->rear=t;
	}
	else
	{//队空
		q->front=t;
		q->rear=t;
	}
}

5.出队

/*
若队列非空,出队,返回1
否则,提示,返回0
*/
int deQueue(LinkQueue *q,ElemType *e)
{
	DataNode *t;
	if(q->front!=NULL||q->rear!=NULL)//非空
	{
		//1.让t指向队头元素节点
		t=q->front;
		//2.把队头元素存储到*e中
		*e=t->data;
		//3,删除队头元素节点
		if(q->front->next==NULL)//只有1个元素
		{
			q->front=NULL;
			q->rear=NULL;
		}
		else
		{//多余1个元素
			q->front=t->next;
		}
		//4.释放t所占的存储空间
		free(t);
		return 1;
	}
	else
	{
		printf("队空,不能出列!\n");
		return 0;
	}
}

6.打印队列中的元素

void DispQueue(LinkQueue *q)
{
	DataNode *p;
	p=q->front;
	printf("队列元素为:");
	while(p!=NULL)
	{
		printf("%d",p->data);
		p=p->next;
	}
	printf("\n");
}

7.销毁队列

void DestroyQueue(LinkQeue *q)
{
	int deQueue(LinkQueue *q,ElemType *e);
	ElemType m;

	while(q->front!=NULL||q->rear!=NULL)//非空
	{
		//出队
		deQueue(q,&m);
	}
	free(q);
}

8.求队列长度

int lengthLinkQueue(LinkQueue *q)
{
	int len;
	if(QueueEmty(%q))
	{
		len = 0;
		return len;
	}
	DataNode *t;
	//1.构造一个节点t,让它指向队首元素front
	t=(DataNode *)malloc(sizeof(DataNode));
	t=q->front;
	len = 1;
	while(t->next != NULL)
	{
		len++;
		t=t->next;
	}
	printf("队列长度为%d\n",len);
}

五、树

二叉树结构一直以来都是数据结构课程中的重点和难点,不论是找工作的笔试面试,还是考研的专业课,二叉树所占的比例都是很大的。
而在原始的二叉树的基础上,有不断演化出了很多其他基于二叉树的结构,例如哈夫曼树、红黑树。B+树等等。但是不管从二叉树衍生出来的结构多么复杂多变,但是底层对于二叉树结构的理论和操作都是相通的
定义:树是n个结点的有限集。n=0时称为空树。
强调以下两点:
1)根结点是唯一的
2)子树的个数没有限制,但它们一定是互不相交的

1.二叉树的结构

typedef int BTDataType;
//二叉链
struct BinaryTreeNode
{
	struct BinTreeNode* _pLeft;	//指向当前节点左孩子
	struct BinTreeNode* _pRight;//指向当前节点右孩子
	BTDataType _data;	//当前节点值域
}

2.二叉树的遍历

二叉树有三种遍历方式,分别为 先序遍历、中序遍历、后序遍历

先序遍历

先访问根节点 再访问左子树 最后访问右子树

void PreOrderTraverse(BiTree T)
{
	if(T == NULL) return;
	printf("%c",T->data);
	PreOrderTraverse(T->Lnode);
	PreOrderTraverse(T->Rnode);
}

中序遍历

先访问左子树 再访问根节点 最后访问右子树

void InOrderTraverse(BiTree T)
{
	if(T == NULL) return;
	InOrderTraverse(T->Lnode);
	printf("%c",T->data);
	InOrderTraverse(T->Rnode);
}

后序遍历

先访问左子树 再访问右子树 最后访问根节点

void PostOrderTraverse(BiTree T)
{
	if(T == NULL) return;
	PostOrderTraverse(T->Lnode);
	PostOrderTraverse(T->Rnode);
	printf("%c",T->data);
}

3.求二叉树所有节点个数(重点!)

int size = 0;
void TreeSize(BTNode* root)//求二叉树结点个数
{
	if(root == NULL)
	{
		return;
	}
	else
	{
		++size;
	}
	TreeSize(root->left);
	TreeSize(root->right);
}

3.求叶子结点的个数

int TreeLeafSize(BTNode* root)
{
	if(root == NULL)
		return 0;
	if(root->left == NULL && root->right == NULL)
		return 1;
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

4.求二叉树的高度

//递归走到树的最底层,再通过比较左右子树的高度,取较高一棵+1,一直累加到树根
int Height(Tree& t){
	if(t == NULL) return 0;//当前结点为空,返回0
	//由下至上比较左右子树高度得到最终高度
	return Height(t->left) > Height(t->right) ? Height(t->left) + 1 : Height(t->right) + 1;
}

5.输出二叉树中的所有叶子节点

void PrintLeaves(BinTree BT)
{
	if(BT)
	{
		if(!BT->Left && !BT->Right)//如果BT节点是叶子
			printf("%d",BT->Data);
		PrintLeaves(BT->Left);
		PrintLeaves(BT->Right);
	}
}

六、查找

什么是查找?
查找:在[数据集]合中寻找满足某种条件的数据元素的过程称为查找
平均查找长度:平均查找长度则是所有查找过程中进行关键字的比较的次数的平均值

1.顺序查找

它的查找过程是:从第一个(或者最后一个)记录开始,逐个进行记录的关键字和给定值进行比较
某个记录的关键字和给定值相等,则查找成功。
如果查找了所有的记录仍然找不到与给定值相等的关键字,则查找不成功。
顺序查找时间复杂度:O(n)

int Search_Seq(SSTable ST, int key){
	//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为
	//该元素在表中的位置,为否为0
	for(int i=ST.length;i>=1;--1)
		if(ST.R[i].key==key) return i;//从后往前找
	return 0;
}

2.带监视哨的顺序查找

具体实现就是将数组的第0位置空,在查找时将要查找的key插入作为监视哨
不用每次循环都检查查找是否结束,减少了元素比较次数,最后的返回值要么是元素下标要么是数组的第0位(这种情况就是到了监视哨)。

int Search_Seq(SSTable ST, int key){
	//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为
	//该元素在表中的位置,为否为0
	ST.R[0].key = key;
	for(int i=ST.length; ST.R[i].key!=key;--i);
	return i;
}

3.拆半查找

拆半查找又称为二分查找,拆半查找的作用对象是有序的查找表,在每次关键字比较时
如果不匹配,则根据匹配结果将查找表一分为二,排除没有关键字的那一半
然后在含有关键字的那一半中继续拆半查找

int Search_Bin(SSTable ST,int key){
	//在有序表ST中拆半查找其关键字等于key的数据元素,若找到,则函数值为
	//该元素在表中的位置,否则为0
	int low = 1,high = ST.length;	//置查找区间初值
	int mid;
	while(low<=high){
		mid = (low+high) / 2;
		if(key==ST.R[mid].key) return mid;		//找到待查元素
		else if(key<ST.R[mid].key) high=mid-1;	//继续在前一子表进行查找
		else low=mid+1;							//继续在后一子表进行查找
	}
	return 0;					//表中不存在待查元素
}

4.插值查找

类似于【二分查找】,但是在找中间值时,不同,插值查找是自适应的mid
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找速度较快

int InsertionSerach(int a[],int value,int low,int high)
{
	int mid = low+(value-a[low]) / (a[high]-a[low]) * (high-low);
	if(a[mid] == value)
		return mid;
	if(a[mid] > value)
		return InsertionSearch(a, value, low, mid-1);
	if(a[mid] < value)
		return InsertionSearch(a, value, mid+1, high);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帷幕Vmu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值