C语言数据结构顺序表、链表、栈、队列、二叉树详解

11 篇文章 1 订阅
1 篇文章 0 订阅

前言

创作不易,先赞后看养成好习惯

数据结构的相关概念

数据结构研究的是数据以及数据之间的关系和运算,即数据结构就是指数据以及数据之间的关系和运算,计算机的操作对象就是数据,数据结构是计算机存储,管理数据的方式。在数据结构中数据元素又称为结点。
数据结构的分类:

  • 集合结构:集合结构中的元素关系是属于同一个集合
    例如,A = { a1,a2,a3,a4,a5 }
    在这里插入图片描述
  • 线性结构:在存储关系上,每个元素最多有一个前驱,一个后继。 一个节点前面的节点叫前驱,后面的节点叫后继
    在这里插入图片描述
  • 树形结构:在存储关系上,每个元素最多有一个前驱,但可以有多个后继。在树形结构中没有前驱的节点是根,没有后继的节点是叶子
    在这里插入图片描述
  • 图形结构:在存储关系上,每个元素可以有多个前驱,多个后继。图形结构更过的是用来写逻辑
    在这里插入图片描述
算法复杂度
  • 时间复杂度:时间复杂度要表达的信息是 算法消耗的时间T 和 数据数量N 的关系,而不是指具体的时间。一般看循环次数和递归次数。
    T(n) 表示数据量为n的时间复杂度
    O() 是时间复杂度的具体表现方式
    例如:fun()的时间复杂度 T(n) = O(n)
int fun(unsigned int n)
{
	if(n==0 || n==1)
		return 1;
	else 
		return n*f(n-1); 
}
  • 空间复杂度:以字节为单位 (1K = 1024 byte) (1M = 1024K) (1G = 1024M) (1T = 1024G) 有些情况下为了提高时间复杂度而牺牲空间复杂度

一、顺序表与链表

1、顺序表

在这里插入图片描述

1.1顺序表的特点
  • 在顺序表中,各元素的逻辑顺序跟物理顺序一致,第i项就存在第i个位置。
  • 对顺序表中的所有元素,既可以顺序访问,也可以随机访问。
  • 顺序表的插入时间复杂度:
    最好情况下T(n) = O(1),当插入结点为顺序表的末尾
    最坏情况下T(n) = O(n),当插入结点为顺序表的头节点
  • 顺序表的随机访问时间复杂度:T(n) = O(1)

主要用数组实现顺序表

1.2顺序表的封装

例:创建一个顺序表保存学生信息,实现增删改查功能

示例代码:

#include <stdio.h>
#include <string.h>
#define MAX_DATA 20

typedef struct//顺序表中结点的结构体类型
{
	char name[20];
	int age;
	int score;
}Stu_t;

typedef struct//顺序表的结构体类型
{
	Stu_t student[MAX_DATA];//顺序表的存储结构,常见的存储结构是数组和链表
	int size;//表示顺序表的有效数据个数	
}List_t;

List_t createList();
int insert(List_t* p,int pos,Stu_t* q);
void printList(List_t* p);
int fullList(List_t* p);
int emptyList(List_t* p);
int lengthList(List_t *p);
void clearList(List_t *p);
int deleteList(List_t* p,int pos);
int changeList(List_t* p,int pos,Stu_t* q);

/*
功能:创建一个顺序表类型结构体变量,并将size成员赋值为0
返回值:返回创建的顺序表结构体变量
*/
List_t createList()
{
	List_t list;
	list.size = 0;
	return list;
}

/*
功能:判断顺序表是否满    数据数量达到数组的最大容量
参数:指向顺序表结构体变量的指针
返回值:满返回1   不满返回0
*/
int fullList(List_t* p)
{
	return p->size==MAX_DATA;
}

/*
功能:判断顺序表是否为空   没有数据就是空
参数:指向顺序表结构体变量的指针
返回值:为空返回1   不为空返回0
*/
int emptyList(List_t* p)
{
	return p->size==0;
}

/*
功能:求顺序表中的元素个数
参数:指向顺序表结构体变量的指针
返回值:顺序表的元素个数
*/
int lengthList(List_t *p)
{
    return p->size;
}

/*
功能:在指定位置pos插入数据x
参数1:指向顺序表结构体变量的指针
参数2:插入数据的位置
参数3:插入的数据
返回值:如果顺序表已满,或者pos位置超出当前数据范围,或者超出顺序表的最大容量,失败返回-1,成功返回0
*/
int insertList(List_t* p,int pos,Stu_t* q)
{
	if(fullList(p)||pos<0||pos>p->size)
	{
		printf("insert error\n");
		return -1;
	}
	int i;
	for(i=p->size;i>pos;i--)//待插入位置后面的元素都往后挪一个位置
	{
		p->student[i] = p->student[i-1];
	}
	strcpy(p->student[i].name,q->name);
	p->student[i].age = q->age;
	p->student[i].score = q->score;
	printf("student[%d] has been inserted\n",pos);
	p->size++;
	return 0;
}

/*
功能:删除指定位置pos的元素
参数1:指向顺序表结构体变量的指针
参数2:删除数据的位置
返回值:如果顺序表为空,或者pos位置超出当前的数据范围,返回-1表示失败,否则返回0表示成功
*/
int deleteList(List_t* p,int pos)
{
	if(pos<0||pos>=p->size)
	{
		printf("delete error\n");
		return -1;
	}
	int i;
	for(i=pos;i<p->size-1;i++)//待删除位置后面的元素都往前挪一个位置
	{
		p->student[i] = p->student[i+1];
	}
	printf("student[%d] has been deleted\n",pos);
	p->size--;
	return 0;
}

/*
功能:修改指定位置pos的元素
参数1:指向顺序表结构体变量的指针
参数2:要修改数据的位置
参数3:新的的数据
返回值:如果顺序表为空,或者pos位置超出当前的数据范围,返回-1表示失败,否则返回0表示成功
*/
int changeList(List_t* p,int pos,Stu_t* q)
{
	if(pos<0||pos>=p->size)
	{
		printf("change error\n");
		return 0;
	}
	strcpy(p->student[pos].name,q->name);
	p->student[pos].age = q->age;
	p->student[pos].score = q->score;
	printf("student[%d] has been changed\n",pos);
}

/*
功能:打印顺序表中的数据
参数:指向顺序表结构体变量的指针
*/
void printList(List_t* p)
{
	int i;
	for(i=0;i<p->size;i++)
	{
		printf("student[%d]:\n",i);
		printf("            name:%s\n",p->student[i].name);
		printf("             age:%d\n",p->student[i].age);
		printf("           score:%d\n",p->student[i].score);
	}
		printf("********************打印完毕********************\n");
}

/*
功能:清空顺序表  size=0就是清空数组,逻辑清空,实际数据还在数组中
参数:指向顺序表结构体变量的指针
*/
void clearList(List_t *p)
{
    p->size = 0;
}

主函数及功能测试:

int main()
{
	Stu_t arr[4] = 
	{
		{"Jack",12,98},
		{"Bob",13,95},
		{"Tom",11,97},
		{"Tony",12,100}
	};
	List_t list = createList();
	int i;
	for(i=0;i<4;i++)
	{
		insertList(&list,i,arr+i);
	}
	printList(&list);//Jack,Bob,Tom,Tony
	deleteList(&list,1);
	printList(&list);//Jack,Tom,Tony
	changeList(&list,2,arr+1);
	printList(&list);//Jack,Tom,Bob
	return 0;
}

运行结果
在这里插入图片描述

2、链表

2.1链表的特点

链表,顾名思义,是一种将一个个携带着数据的结点链接起来的数据结构,这些结点在物理存储上可以是非连续的,结点的基本结构由数据域和指针域组成,各个结点的链接正是通过对指针域的操作实现的。我们通常用结构体定义结点。
在这里插入图片描述

1、单向链表:每个节点只有一个指针指向下一个节点,上图即为单链表
2、双向链表:每个节点有两个指针,分别指向下一个节点和上一个节点
3、单向循环链表:单向链表的基础上,让最后一个节点指向第一个节点
4、双向循环链表:双向链表的基础上,连接最后一个结点和第一个结点

  • 链表插入结点时间复杂度:T(n) = O(1)
  • 链表随机访问结点时间复杂度:T(n) = O(n)
2.2链表的封装
单向链表

保姆级单链表详解转到:数据结构之线性表——链表 C语言版
示例代码:

#include<stdio.h>
#include<stdlib.h>

typedef struct node_t
{
	int data;
	struct node_t* next;
}LinkNode_t;

LinkNode_t* createLinkList();
int emptyLinkList(LinkNode_t* p);
int lengthLinkList(LinkNode_t* p);
int insertLinkList(LinkNode_t* p,int pos,int x);
void printLinkList(LinkNode_t* p);
int getLinkList(LinkNode_t* p,int pos);
int deleteLinkList(LinkNode_t* p,int pos);
void clearLinkList(LinkNode_t* p);
void ReversePrintList(LinkNode_t* p);

/*
 功能:创建一个链表的空头结点(堆空间)返回头结点的地址
 参数:新结点的值
 返回值:空链表的头结点指针
 */
LinkNode_t* createLinkList()
{
	LinkNode_t* node =(LinkNode_t*)malloc(sizeof(LinkNode_t));
	node->next = NULL;
	return node;
}

/* 
功能:判断链表是否为空
参数:链表头结点指针
返回值:空返回1 非空返回0
*/
int emptyLinkList(LinkNode_t* p)
{
	return p->next == NULL;
}

/*
 功能:计算链表中除空头结点之外的结点个数
 参数:链表头结点指针
 返回值:结点个数
 */
int lengthLinkList(LinkNode_t* p)
{
	int count = 0;
	LinkNode_t* t = p->next;
	while(t!=NULL)
	{
		count++;
		t = t->next;
	}
	return count;
}

/*
功能:在链表中插入新结点并存入数据
参数1:链表头结点指针
参数2:插入的位置(第一个有数据的结点位置为0)
参数3:新结点的值
返回值:成功返回0 失败返回-1
*/
int insertLinkList(LinkNode_t* p,int pos,int x)
{
	if(pos<0||pos>lengthLinkList(p))
	{	
		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;
}
/*
功能:获得链表中某结点的值
参数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++)
	{
		t = t->next;
	}
	return t->data;
}

/*
功能:遍历输出链表中结点的值
参数:链表头结点指针
返回值:无
*/
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++;
	}
}
/*
功能:删除参数位置结点
参数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;
}

/*
功能:清空链表
参数:链表头结点指针
*/
void clearLinkList(LinkNode_t* p)
{
	while(p!=NULL)
	{
		LinkNode_t* t = p;
		p = p->next;
		free(t);
	}
}
/*
功能:反向打印链表
参数:链表头结点指针
*/ 
void ReversePrintList(LinkNode_t* p)
{
	static int count=0;
	if(p->next!=NULL)
	{
		count++;
		ReversePrintList(p->next);
		count--;
		printf("NODE[%d]:%d\n",count,p->next->data);
		return;
	}
	return;
}

主函数及功能测试:

int main()	
{	
	LinkNode_t* head = createLinkList();
	insertLinkList(head,0,10);//10
	insertLinkList(head,0,20);//20 10
	insertLinkList(head,1,30);//20 30 10
	insertLinkList(head,3,40);//20 30 10 40
	printLinkList(head);//20 30 10 40
	insertLinkList(head,4,50);//20 30 10 40 50
	printf("\n%d\n",getLinkList(head,0));//20
	deleteLinkList(head,0);//30 10 40 50
	printLinkList(head);
	return 0;
}

运行结果
在这里插入图片描述

双向链表

双向链表的特点:

  • 每个结点都有一个前驱指针和一个后继指针,比单向链表的空间复杂度更大
  • 双向链表既可以向后遍历又可以向前遍历
    在这里插入图片描述
    示例代码:
#include <stdio.h>
#include <stdlib.h>

typedef struct node_t	//双链表结点的定义
{
	int data;//数据域
	struct node_t* pre;//指向前驱的指针
	struct node_t* next;//指向后继的指针
}LinkNode_t;

LinkNode_t* createNode(int x);
int lenthList(LinkNode_t* p);
void printLinkList(LinkNode_t* p);
void insertNodeBack(LinkNode_t* p,int pos,int x);
void insertNodeFront(LinkNode_t** p,int pos,int x);
void chageNode(LinkNode_t* p,int pos,int x);
void deleteNode(LinkNode_t** p,int pos);
void deleteLinkList(LinkNode_t* p);

/*
功能:创建一个结点并存入数据
参数:存入结点的数据
返回值:返回新创建的结点的指针
*/
LinkNode_t* createNode(int x)
{
	LinkNode_t* node = (LinkNode_t*)malloc(sizeof(LinkNode_t));
	if(node == NULL)
	{
		printf("malloc error\n");
		return;
	}
	node->pre = NULL;
	node->next = NULL;
	node->data = x;
	return node;
}

/*
功能:计算并返回链表结点个数,包括头结点
参数:头结点指针
返回值:链表结点数量
*/
int lenthList(LinkNode_t* p)
{
	int count = 0;
	LinkNode_t* temp = p;
	while(temp !=NULL)
	{
		count++;
		temp = temp->next;
	}
	return count;
}

/*
功能:打印链表所有结点
参数:头结点指针
返回值:无
*/
void printLinkList(LinkNode_t* p)
{
	LinkNode_t* temp = p;
	while(temp!=NULL)
	{
		printf("%d->",temp->data);
		temp = temp->next;
	}
	printf("\n");
}

/*
功能:在参数2结点位置后面插入新结点
参数1:头结点指针
参数2:结点的位置(头结点位置为0)
参数3:新结点存入的数据
返回值:无
*/
void insertNodeBack(LinkNode_t* p,int pos,int x)//在pos位置后面插入结点
{
	if(pos<0||pos>lenthList(p)-1)//pos最大取到链表最后一个结点的位置
	{
		printf("position error\n");
		return;
	}
	LinkNode_t* newNode = createNode(x);
	LinkNode_t* temp = p;
	int i;
	for(i=0;i<pos;i++)
	{
		temp = temp->next;
	}
	if(temp->next == NULL)//temp已经是最后一个结点,特殊考虑
	{
		temp->next = newNode;
		newNode->pre = temp;
	}
	else
	{
		newNode->next = temp->next;
		newNode->pre = temp;
		temp->next->pre = newNode;
		temp->next = newNode;
	}
	printf("在第%d结点后插入结点%d\t\t",pos,x);
	printLinkList(p);
}

/*
功能:在参数2结点位置前面插入新节点
参数1:头结点指针
参数2:结点的位置(头结点位置为0)
参数3:新结点存入的数据
返回值:无
*/
void insertNodeFront(LinkNode_t** p,int pos,int x)//在pos位置前面插入结点
{							//head可能被改变,这里传head地址进来
	if(pos<0||pos>=lenthList(*p))
	{
		printf("position error\n");
		return;
	}
	LinkNode_t* newNode = createNode(x);
	LinkNode_t* temp = *p;
	if(pos == 0)//在第0位置前面插结点
	{
		newNode->next = temp;
		temp->pre = newNode;
		*p = newNode;	//改变head为新插入结点的地址
	}
	else
	{
		int i;
		for(i=0;i<pos;i++)
		{
			temp = temp->next;	
		}
		newNode->next = temp;
		newNode->pre = temp->pre;
		temp->pre->next = newNode;
		temp->pre = newNode;
	}
	printf("在第%d结点前插入结点%d\t\t",pos,x);
	printLinkList(*p);
}

/*
功能:改变参数2结点存储的数据
参数1:链表头结点指针
参数2:结点位置(头结点位置为0)
参数3:改变后的数据
返回值:无
*/
void chageNode(LinkNode_t* p,int pos,int x)
{
	if(pos<0||pos>lenthList(p)-1)
	{
		printf("position error\n");
		return;
	}
	LinkNode_t* temp = p;
	int i;
	for(i=0;i<pos;i++)
	{
		temp = temp->next;
	}
	temp->data = x;
	printf("第%d结点存储的值改为%d\t\t",pos,x);
	printLinkList(p);
}

/*
功能:删除参数2位置的结点
参数:头结点指针
返回值:无
*/
void deleteNode(LinkNode_t** p,int pos)//删除pos位置的结点
{
	if(pos<0||pos>lenthList(*p)-1)
	{
		printf("position error\n");
	}
	LinkNode_t* temp = *p;
	int i;
	for(i=0;i<pos;i++)
	{
		temp = temp->next;
	}
	if(temp->pre == NULL)//如果要删除头结点
	{
		temp->next->pre = NULL;
		*p = temp->next;
		free(temp);
	}
	else if(temp->next == NULL)//如果要删除尾结点
	{
		temp->pre->next = NULL;
		free(temp);
	}
	else
	{
		temp->pre->next = temp->next;
		temp->next->pre = temp->pre;
	}
	printf("删除第%d结点\t\t\t",pos);
	printLinkList(*p);
}


/*
功能:删除整个链表(释放内存)
参数:头结点指针
返回值:无
*/
void deleteLinkList(LinkNode_t* p)
{
	LinkNode_t* temp = p;
	while(temp!=NULL)
	{
		LinkNode_t* q = temp;
		temp = temp->next;
		free(q);
	}
	printf("链表已删除!\n");
}

主函数及功能测试:

int main()
{
	LinkNode_t* head = createNode(100);//100
	printf("创建链表:\t\t\t");
	printLinkList(head);
	insertNodeBack(head,0,101);//100,101
	insertNodeBack(head,1,102);//100,101,102
	insertNodeFront(&head,0,103);//103,100,101,102
	insertNodeFront(&head,3,104);//103,100,101,104,102
	chageNode(head,1,105);//103,105,101,104,102
	deleteNode(&head,2);//103,105,104,102
	deleteLinkList(head);
	return 0;
}

运行结果
在这里插入图片描述

二、栈与队列

栈模型

  • 栈(stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶(top)。
  • 对栈的基本操作有进栈(Push)和出栈(Pop),前者相当于插入,后者则是删除最后插入的元素。
  • 最后插入的元素可以通过使用Top例程在执行Pop之前进行访问。
  • 栈不支持随机访问。

栈有时又叫做LIFO(后进先出)表
栈的插入:叫做进栈(也叫压栈、入栈),时间复杂度为O(1)
栈的删除:叫做出栈(也叫弹栈),时间复杂度为O(1)

在这里插入图片描述

栈的实现
栈主要有数组(顺序栈)和链表(链式栈简称链栈)两种实现方式

顺序栈与链式栈的对比
相同:
(1) 时间复杂度均为O(1)
(2) 都是在栈顶进行出入栈操作
不同:
(1) 从空间性来讲:顺序栈需要先确定一个固定的长度,也许会出现内存空间浪费的情况。
(2) 从存取定位来讲:顺序栈存取时定位很方便,而链栈要求每个元素都有指针域,增加了内存开销。
总结:
元素变化不确定,最好使用链栈。如果元素的变化都在可控范围则使用顺序栈。

3、顺序栈

3.1顺序栈的封装

示例:

#include<stdio.h>
#include<stdlib.h>

typedef struct //栈对象的定义
{
	int* data;	//数组的指针
	int maxlen;	//数组大小,也是栈的大小
	int top;	//逻辑栈顶
}stack_t;

stack_t *createEmptyStack(int len);
void clearStack(stack_t* p);
void destoryStack(stack_t* p);
int isEmpty(stack_t* p);
int isFull(stack_t* p);
void pushStack(stack_t* p,int x);
void popStack(stack_t* p);
int getTopElem(stack_t* p);
void visitStack(stack_t* p);

/*
功能:创建个空的顺序栈 在堆空间申请内存
参数:栈的最大容量
返回值:堆空间内存的指针
*/
stack_t *createEmptyStack(int len)
{
	stack_t* stack = (stack_t*)malloc(sizeof(stack_t));//创建栈对象
	stack->data = (int*)malloc(len*sizeof(int));	   //动态创建数组
	stack->maxlen = len;
	stack->top = 0;//栈顶默认为0,因为没有数据
	printf("容量为%d的空栈已创建\n",len);
	return stack;
}

/*
功能:清空栈数据,将栈顶角标置0,惰性删除(数据还在,逻辑上删除)
参数:栈结构体的指针
*/
void clearStack(stack_t* p)
{
	p->top = 0;
	printf("栈已清空\n");
}

/*
功能:删除栈对象,删除动态数组
参数:栈结构体指针
*/
void destoryStack(stack_t* p)
{
	free(p->data);  //释放动态数组
	free(p);		//释放栈对象
	printf("栈已销毁\n");
}

/*
功能:判断栈是否为空
参数:栈结构体指针
返回值:空返回1 非空返回0
*/
int isEmpty(stack_t* p)
{
	return p->top == 0;
}

/*
功能:判断栈是否满
参数:栈结构体的指针
返回值:满返回1 非满返回0
*/
int isFull(stack_t* p)
{
	return p->top == p->maxlen;
}

/*
功能:将数据x压入栈顶
参数1:栈结构体的指针
参数2:压入的数据
*/
void pushStack(stack_t* p,int x)
{
	if(isFull(p))
	{
		printf("栈已满,进栈失败\n");
		return;
	}
	p->data[p->top++] = x;
	printf("  %d 已入栈\n",x);
}

/*
功能:移除栈顶元素
参数:栈结构体的指针
*/
void popStack(stack_t* p)
{
	if(isEmpty(p))
	{
		printf("栈已空,出栈失败\n");
		return;
	}
	printf("  %d 已出栈\n",p->data[p->top-1]);
	p->top--;
}

/*
功能:查看栈顶元素
参数:栈结构体的指针
返回值:栈顶元素的值
*/
int getTopElem(stack_t* p)
{
	if(isEmpty(p))
		return -1;
	return p->data[p->top-1];
}

/*
功能:查看栈顶元素
参数:栈结构体的指针
返回值:栈顶元素的值
*/
void visitStack(stack_t* p)
{
	printf("栈底 ");
	int i;
	for(i=0;i<p->top;i++)
	{
		printf(" %d ",p->data[i]);
	}
	printf(" 栈顶\n");
}

主函数及功能测试

int main()
{
	stack_t* stack = createEmptyStack(10);
	int arr[10] = {0,1,2,3,4,5,6,7,8,9};
	int i;
	for(i=0;i<10;i++)
	{
		pushStack(stack,arr[i]);//把数组中10个元素入栈
	}
	pushStack(stack,10);//栈满,进栈失败
	popStack(stack);//9出栈
	visitStack(stack);//查看栈内情况
	clearStack(stack);清空栈
	popStack(stack);//栈空,出栈失败
	visitStack(stack);
	destoryStack(stack);销毁栈
	return 0;
}

运行结果:
在这里插入图片描述

4、链式栈

4.1链式栈的封装

示例:

#include <stdio.h>
#include <stdlib.h>

typedef struct node_t
{
	int data;
	struct node_t* next;
}LinkNode_t;

typedef struct stack_t//定义栈对象
{
	LinkNode_t* head;
	int top;
}Stack_t;

Stack_t* createEmptyStack();
int isEmpty(Stack_t* p);
void pushStack(Stack_t* p,int x);
void popStack(Stack_t* p);
void visitStack(Stack_t* p);


/*
功能:创建一个空栈(0个结点)
参数:无
返回值:栈对象指针
*/
Stack_t* createEmptyStack()
{
	Stack_t* stack = (Stack_t*)malloc(sizeof(Stack_t));
	stack->head = NULL;
	stack->top = 0;
	return stack;
}

/*
功能:判断栈是否为空
参数:栈对象指针
返回值:空返回1,非空返回0
*/
int isEmpty(Stack_t* p)
{
	return p->top == 0;
}

/*
功能:压栈
参数1:栈对象指针
参数2:新压入栈的结点保存的数据
返回值:无
*/
void pushStack(Stack_t* p,int x)
{
	 LinkNode_t* newNode = (LinkNode_t*)malloc(sizeof(LinkNode_t));
	 newNode->next = NULL;
	 newNode->data = x;
	 if(isEmpty(p))
	 {
	 	p->head = newNode;
	 }
	 else
	 {
		LinkNode_t* temp = p->head;
	 	int i;
		for(i=0;i<p->top-1;i++)
		{
			temp = temp->next;
		}
		temp->next = newNode;
	 }
	 p->top++;
	 printf(" %d 已入栈\n",x);
}

/*
功能:出栈,释放栈顶元素的内存
参数:栈对象指针
返回值:无
*/
void popStack(Stack_t* p)
{
	if(isEmpty(p))
	{
		printf("栈已空\n");
		return;
	}
	else if(p->top == 1)
	{
		printf(" %d 已出栈\n",p->head->data);
		free(p);
		p->top--;
	}
	else
	{	
		LinkNode_t* temp = p->head;
		int i;
		for(i=0;i<p->top-2;i++)//找到待出队的前一个结点
		{
			temp = temp->next;
		}
		LinkNode_t* S = temp->next;//先保存下来
		printf(" %d 已出栈\n",temp->next->data);
		temp->next = NULL;
		free(S);
		p->top--;
	}
}

/*
功能:查看栈内结点的信息
参数:栈对象指针
返回值:无
*/
void visitStack(Stack_t* p)
{
	printf(" 栈底 ");
	int i;
	LinkNode_t* temp = p->head;
	while(temp!=NULL)
	{
		printf(" %d->",temp->data);
		temp = temp->next;
	}
	printf(" 栈顶 \n");
}

主函数及功能测试:

int main()
{
	int a[10] = {0,1,2,3,4,5,6,7,8,9};
	Stack_t* stack = createEmptyStack();
	int i;
	for(i=0;i<10;i++)
	{
		pushStack(stack,a[i]);//将数组a中的元素依次压入栈
	}
	visitStack(stack);
	for(i=0;i<2;i++)
	{
		popStack(stack);//两次出栈操作
	}
	visitStack(stack);
	return 0;
}

运行结果:
在这里插入图片描述

5、队列

队列模型

  • 像栈一样,队列(queue)也是表。然而,使用队列时插入(入队)在一端进行,而删除(出队)在另一端进行。
  • 队列的基本操作:入队,是在表的末端(队尾rear)插入一个元素;出队:是删除(或返回)在表的开头(队头front)的元素
    在这里插入图片描述
    循环队列:
    观察上图发现,当rear指向 6 元素位置时,队列似乎已满,不能再进行入队(rear++)的操作了,但 0 、1 元素位置是已经出队的,逻辑上无效的元素。这时rear可以绕回来到 0 的位置,继续进行入队操作。具体实现可以参考下面循环队列的示例。

队列通常由数组或者链表实现
队列的时间复杂度为O(1)

5.1顺序队列

示例:基于顺序数组实现循环队列

#include <stdio.h>
#include <stdlib.h>

typedef struct Queue//定义队列对象
{
	int* data;
	int maxlen;
	int front;
	int rear;
}Queue_t;

Queue_t* createEmptyQueue(int len);
int isEmpty(Queue_t* p);
int isFull(Queue_t* p);
void pushQueue(Queue_t* p,int x);
void popQueue(Queue_t* p);
int topQueue(Queue_t* p);
void visitQueue(Queue_t* p);

/*
功能:在堆空间创建队列对象
参数:队列大小
返回值:堆空间中队列对象指针
*/
Queue_t* createEmptyQueue(int len)
{
	Queue_t* queue  = (Queue_t*)malloc(sizeof(Queue_t));
	if(queue==NULL)
		return NULL;
	queue->data = (int*)malloc(len*sizeof(int));
	if(queue->data==NULL)
		return NULL;
	queue->maxlen = len;
	queue->front = 0;
	queue->rear = 0;
	printf("长度为%d的队列已在堆空间创建\n",len);
	return queue;
}

/*
功能:判断队列是否为空
参数:队列结构体指针
返回值:空返回1 非空返回0
*/
int isEmpty(Queue_t* p)
{
	return p->front == p->rear;
}

/*
功能:判断队列是否为满
参数:队列结构体指针
返回值:满返回1 非满返回0
*/
int isFull(Queue_t* p)
{
	return (p->rear+1)%(p->maxlen) == p->front;
}

/*
功能:在队列rear位置插入一个数据
参数1:结构体队列指针
参数2:待插入的数据
*/
void pushQueue(Queue_t* p,int x)
{
	if(isFull(p))
	{
		printf(" 队列已满\n");
		return;
	}
	p->data[p->rear] = x;
	p->rear = (p->rear+1)%(p->maxlen);
	printf(" %d 已入队\n",x);
}

/*
功能:移动front到下一个位置
参数:队列结构体指针
*/
void popQueue(Queue_t* p)
{
	if(isEmpty(p))
	{
		printf(" 队列已空\n");
		return;
	}
	printf(" %d 已出队\n",p->data[p->front++]);
}

/*
功能:获得将要出队的元素(队顶元素)
参数:队列结构体指针
*/
int topQueue(Queue_t* p)
{
	if(isEmpty(p))
		return -1;
	printf(" 当前队顶元素为%d\n",p->data[p->front]);
	return p->data[p->front];
}

/*
功能:查看队列内元素信息
参数:队列结构体指针
*/
void visitQueue(Queue_t* p)
{
	printf(" 队头 |");
	int i = p->front;
	while(i != p->rear)
	{
		printf(" %d |",p->data[i]);
		i = (i+1)%(p->maxlen);
	}
	printf(" 队尾\n");
}

主函数及功能测试:

int main()
{
	Queue_t* queue = createEmptyQueue(10);
	int a[10] = {0,1,2,3,4,5,6,7,8,9};
	int i;
	for(i=0;i<10;i++)
	{
		pushQueue(queue,a[i]);//将数组a中的元素依次入队
	}
	visitQueue(queue);//查看队列
	for(i=0;i<5;i++)
	{
		popQueue(queue);//5次出队操作
		topQueue(queue);
	}
	visitQueue(queue);
	for(i=0;i<5;i++)
	{
		pushQueue(queue,a[i]);//5次入队操作
	}
	visitQueue(queue);
	return 0;
}

运行结果:
在这里插入图片描述

5.2链式队列

参考链式栈

三、二叉树

树形结构
在这里插入图片描述
根结点: 只有后继,没有前驱结点 ,上图中 root 是根结点
叶子结点: 只有前驱,没有后继结点 上图中 C D E F 是叶子结点

二叉树:
每个节点最多有两个子结点,如果只有一个子结点,那么也要区分 左子结点还是右子结点(也通常说左孩子和右孩子)

二叉树的性质:

  • 二叉树第i(i≥1)层上的节点最多为 2 ^(i-1) 个结点。 例如 第3层上最多有4个结点
  • 深度为k(k≥1)的二叉树最多有 2^k-1 个结点。例如深度为3层的二叉树,最多有7个结点
  • 在任意一棵二叉树中,度为0的结点即(n0)的数目比度为2的结点(n2)的数目多1(度数是一个结点的后继结点的个数)

满二叉树:
深度为k(k≥1)时有 2^k -1 个结点的二叉树
在这里插入图片描述

完全二叉树:
在满二叉树的基础上,最后一层的叶子结点都集中在左边(不能有空位)

6、二叉树的遍历

遍历二叉树:沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次

6.1深度优先遍历
  • 先序遍历(前序遍历) 根 --> 左 -->右

先序遍历可以想象为,一个小人从一棵二叉树根节点为起点,沿着二叉树外沿,逆时针走一圈回到根节点,路上遇到的元素顺序,就是先序遍历的结果
在这里插入图片描述
先序遍历结果为:A B D H I E J C F K G

  • 中序遍历 左 --> 根 -->右

中序遍历可以看成,二叉树每个节点,垂直方向投影下来(可以理解为每个节点从最左边开始垂直掉到地上),然后从左往右数,得出的结果便是中序遍历的结果
在这里插入图片描述
中遍历结果为:H D I B E J A F K C G

  • 后序遍历 左 -->右 --> 根

后序遍历就像是剪葡萄,我们要把一串葡萄剪成一颗一颗的。如果发现一剪刀就能剪下的葡萄(必须是一颗葡萄)(也就是葡萄要一个一个掉下来,不能一口气掉超过1个这样),就把它剪下来,组成的就是后序遍历
在这里插入图片描述
后序遍历结果:H I D J E B K F G C A

代码实现

#include<stdio.h>
#include<stdlib.h>
typedef struct Note_t//结点定义
{
	char data;
	struct Note_t* lchild;
	struct Note_t* rchild;
}Bitree_t;

void firstVisit(Bitree_t* p)
void midVisit(Bitree_t* p)
void lastVisit(Bitree_t* p)

void firstVisit(Bitree_t* p)//先序遍历
{
	if(p==NULL)
		return;
	printf("%c ",p->data);
	firstVisit(p->lchild);
	firstVisit(p->rchild);
}

void midVisit(Bitree_t* p)//中序遍历
{
	if(p==NULL)
		return;
	midVisit(p->lchild);
	printf("%c ",p->data);
	midVisit(p->rchild);
}

void lastVisit(Bitree_t* p)//后序遍历
{
	if(p==NULL)
		return;
	lastVisit(p->lchild);
	lastVisit(p->rchild);
	printf("%c ",p->data);
}

主函数及功能测试:

int main()
{
	Bitree_t A = {'A',NULL,NULL};
	Bitree_t B = {'B',NULL,NULL};
	Bitree_t C = {'C',NULL,NULL};
	Bitree_t D = {'D',NULL,NULL};
	Bitree_t E = {'E',NULL,NULL};
	Bitree_t F = {'F',NULL,NULL};
	A.lchild = &B;
	B.lchild = &D;
	A.rchild = &C;
	C.lchild = &E;
	C.rchild = &F;
	firstVisit(&A);
	printf("\n");
	midVisit(&A);
	printf("\n");
	lastVisit(&A);
	printf("\n");
	return 0;
}

运行结果:
在这里插入图片描述

6.2广度优先遍历

广度遍历也叫层序遍历,从根节点开始,一层一层,从上到下,每层从左到右
在这里插入图片描述
代码实现:

需要用到队列,先把根结点入队,而后循环出队,检查出队结点是否有子结点,如果有,出队的同时把它的子结点入队,队列为空时遍历结束

#include<stdio.h>
#include<stdlib.h>
typedef struct Note_t
{
	char data;
	struct Note_t* lchild;
	struct Note_t* rchild;
}Bitree_t;


void bfs(Bitree_t* p)
{
	Bitree_t* a[20];	//队列
	int front = 0,rear = 0;
	a[rear++] = p;		//根结点入队
	while(front!=rear)
	{
		if(a[front]->lchild!=NULL)
		{
			a[rear++] = a[front]->lchild;//左孩子入队
		}
		if(a[front]->rchild!=NULL)
		{
			a[rear++] = a[front]->rchild;//右孩子入队
		}
		printf("%c ",a[front++]->data);		//循环出队
	}
}


int main()
{
	Bitree_t A = {'A',NULL,NULL};
	Bitree_t B = {'B',NULL,NULL};
	Bitree_t C = {'C',NULL,NULL};
	Bitree_t D = {'D',NULL,NULL};
	Bitree_t E = {'E',NULL,NULL};
	Bitree_t F = {'F',NULL,NULL};
	A.lchild = &B;
	B.lchild = &D;
	A.rchild = &C;
	C.lchild = &E;
	C.rchild = &F;
	bfs(&A);
	printf("\n");
	return 0;
}

运行结果:
在这里插入图片描述

结尾

  • 笔者水平有限,有错误的地方请大家见谅
  • 欢迎大家留言评论
  • IT培训就来 华清远见 http://www.hqyj.com/
    在这里插入图片描述
  • 15
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值