数据结构学习笔记

 

目录

 

第一部分 数据结构概述

  一、定义(研究是数据结构的存储和数据的操作的)

  二、算法

  三、数据结构的地位

第二部分 预备知识

  一、指针

  二、结构体

  三、动态内存的分配和释放

第三部分 模块一:线性结构 [把所有的结点用一条直线穿起来]

  一、连续存储[数组]

  二、离散存储[链表]

  三、线性结构的两种常见应用之一  栈

  四、线性结构的两种常见应用之一  队列

  五、专题:递归(使用栈实现的)

第四部分 模块二:非线性结构

  一、树

第五部分、代码(C语言)

1、数组

2、链表

3、栈(链表结构)

4.队列(循环数组)

5.递归(汉诺塔)

6、链式二叉树

7、快速排序


第一部分 数据结构概述

  一、定义(研究是数据结构的存储和数据的操作的)

    如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也叫做算法。

数据结构 = 个体的存储(从某个角度而言,可忽略) + 个体与个体之间关系的存储(核心)

算法 = 对存储数据的操作

  二、算法

解题的方法和步骤

衡量算法的标准

1、时间复杂度

         大概程序要执行的次数,而非执行的时间

2、空间复杂度

           算法执行过程中大概所占用的最大内存

 3、难易程度(即可读性)

 4、健壮性

  三、数据结构的地位

     数据结构是软件中最核心的内容

  程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

第二部分 预备知识

  一、指针

  指针的重要性:指针是C语言的灵魂

定义

  地址:内存单元的编号

         从零开始的非负整数

         范围:0--FFFFFFFF(即0--4G-1)

   指针:

                指针就是地址,地址就是指针

                指针变量是存放内存单元地址的变量

                指针的本质是一个操作受限的非负整数(只能进行减运算)

   分类:

  1. 基本类型的指针
  2. 指针和一维数组的关系

  二、结构体

 为什么会出现结构体

 为了表示一些复杂的数据,而普通的基本类型变量无法满足要求

 什么叫结构体

 结构体是用户根据实际需要自己定义的数据类型

 如何使用结构体

   两种方式:

        struct Student st = {1000, "zhangsan", 20}

         struct Student * pst = &st;

   1.st.sid

   2.pst->sid

     pst所指向的结构体变量中的sid这个成员

 注意事项

   结构体变量不能加减乘除,但可以相互赋值

   普通结构体变量和结构体指针变量作为函数传参问题

  三、动态内存的分配和释放

 假设动态构造一个int型的一位数组

 int  len;

 int * pArr = (int *)malloc (sizeof(int) * len);

 ①本语句分配了两块内存,一块内存是动态分配的,总共len个字节;另一块是静态分配的,是pArr变量本身所占的内存,总共4个字节。

 ②malloc只有一个int型的形参,表示要求系统分配的字节数

 ③malloc函数的功能是请求系统分配len个字节的内存空间,如果分配成功,则返回第一个字节的地址,如果分配不成功,则返回NULL

 ④malloc函数能且只能返回第一个字节的地址,所以我们需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此,malloc函数前面必须加强制类型转换(数据类型 *),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址。

 ⑤free(* pArr)

 表示把pArr所指向的内存给释放掉

 pArr本身的内存是静态的,不能有程序员手动释放,只能在pArr变量所在的函数运行终止时有系统自动释放

 ⑥跨函数使用内存

静态内存不可以跨函数使用:

静态内存在函数执行期间可以被其它函数使用

静态内存在函数执行完毕之后就不能在被其它函数使用

动态内存可以跨函数使用

动态内存在函数执行完毕之后仍然可以被其它函数使用

第三部分 模块一:线性结构 [把所有的结点用一条直线穿起来]

  一、连续存储[数组]

  1. 什么叫做数组

 元素类型相同,大小相等

   2.数组的优缺点(相对于链表)

                 优点:存取速度快

                 缺点:实现必须知道数组的长度

                           需要大块连续的内存块

                           插入和删除元素很慢

                           空间通常是有限制的  

  二、离散存储[链表]

1.定义

N个结点离散分配

彼此通过指针相连

每个结点只有一个前驱结点,每个结点只有一个后续结点

首结点没有前驱结点,尾结点没有后续结点

 专业术语:

首结点:第一个存放有效数据的结点

尾结点:最有一个存放有效数据的结点

头结点:头结点的数据类型和首结点的类型是一样的

              首结点之前的结点

              头结点并不存放有效数据

              加头结点的目的是为了方便对链表的操作

头指针:指向头结点的指针变量

尾指针:指向尾结点的指针变量

 确定一个链表需要几个参数

(如果希望通过一个函数来对链表进行处理,我们至少需要接受链表的那些参数)

一个参数,即头指针,因为我们通过头指针可以推算出链表的其它所有的信息

2.分类

单链表

双链表:每一个结点有两个指针域

 

循环链表:能通过任何一个结点找到其它所有的结点

非循环链表

3.算法

遍历 / 查找 / 清空 / 销毁 / 求长度 / 排序 / 删除结点 / 插入结点

插入结点:(伪算法)

① r = p->pNext; p->pNext = q; q->pNext = r;

② q->pNext = p->pNext; p->pNext = q;(这两行代码不能倒过来)

删除结点:(伪算法)

r = p->pNext; p->pNext = p->pNext->pNext; free(r);

算法:

  狭义的算法是与数据的存储方式密切相关

  广义的算法是与数据的存储方式无关

  泛型:

         利用某种技术达到的效果就是:不同的存储方式,执行的操作是一样的

       同一种逻辑结构(包括线性结构和非线性结构,其中非线性结构包括树和图),无论该逻辑结构物理存储(包括数组和链表)是什么样子的,我们都可以对它执行相同的操作

4、链表的优缺点(相对于数组)

优点:空间没有限制

           插入和删除元素很快

缺点:存取的速度很慢

  三、线性结构的两种常见应用之一  栈

定义:一种可以实现“先进后出”的存储结构                栈类似于箱子

分类

静态栈:以数组为内核

动态栈:以链表为内核

算法

出栈 / 入栈

应用

函数调用 / 中断 / 表达式求值 /内存分配 / 缓冲处理 / 迷宫

  四、线性结构的两种常见应用之一  队列

定义:一种可以实现“先进先出”的存储结构

  队列类似于排队买票

分类

链式队列 ---- 用链表实现

静态队列 ---- 用数组实现

           静态队列通常都必须是循环队列

循环队列的讲解:

①静态队列为什么必须是循环队列

             普通队列的参数front和rear只增不减,导致内存浪费

②循环队列需要几个参数来确定

             需要两个参数:front / rear

③循环队列各个参数的含义

两个参数在不同的场合有不同的含义

①队列初始化

front 和 rear 的值都为零

②队列非空

front 代表的是队列的第一个元素

rear 代表的是队列的最后一个有效元素的下一个元素

③队列空

front 和rear 的值相等,但不一定是零

④循环队列入队伪算法讲解(在尾部入队)

第一步:将值存入rear所指向的位置

第二步:rear = (rear + 1)%数组的长度

⑤循环队列出队伪算法讲解(在头部出队)

front = (front +1)%数组的长度

⑥如何判断循环队列为空

如果front 与rear的值相等,则该队列一定为空

⑦如何判断循环队列为满

第一种方法:多增加一个标识参数,即数组的长度

(常用)第二种方法:少用一个元素,如果front == (rear + 1)%数组的长度,则循环队列已满

算法

出队 / 入队

应用

所有和时间有关的操作都有队列的影子

  五、专题:递归(使用栈实现的)

1、定义:一个函数自己直接或间接调用自己

2、函数的调用

当在一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事:

  1. 将所有的实际参数、返回地址等信息传递给被调函数保存
  2. 为被调函数的局部变量(包括形参)分配存储空间
  3. 将控制转移到被调函数的入口

从被调函数返回主调函数之前,系统也要完成三件事:

  1. 保存被调函数返回结果
  2. 释放被调函数所占的存储空间
  3. 依照被调函数保存的返回地址将控制转移到调用函数

当有多个函数相互调用时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制转移必须借助“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作;每当一个函数退出时,就释放它的存储区,进行出栈操作,当前运行的函数永远都在栈顶位置。

 A函数调用A函数与A函数调用B函数在计算机看来是没有任何区别的,只不过用我们日常的思维方式理解比较怪异而已。

3、递归必须满足的三个条件:

    a、递归必须得有一个明确的终止条件

   b、该函数处理的数据规模必须在递减

   c、这个转化必须是可解的

循环和递归之间的关系

理论上循环能解决的问题,肯定可以转化为递归解决,但是这个过程是复杂的数学转化过程,递归能解决的问题不一定能转化为循环解决

递归的优缺点:

易于理解

速度慢

存储空间大

循环的优缺点:

不易理解

速度快

存储空间小

5、举例:

汉诺塔

伪算法:

if(1 == n)

直接将盘子从A移动到C

else

{(三步)

第一步:将A柱子上的前n - 1个盘子借助C移动到B

第二步:将A柱子上的第n个盘子直接移动到C

第三步:将B柱子上的n - 1个盘子借助A移动到C

}

  6、递归的应用

树和森林就是以递归的方式定义的

树和图的很多算法都是以递归来实现的

很多数学公式就是以递归的方式定义的(例如:斐波拉契序列)

第四部分 模块二:非线性结构

  一、树

  1. 定义

(专业定义)有且只有一个称为根的结点;有若干个互不相交的子树,这些子树本身也是一棵树

(通俗定义)树是由结点和边(即指针)组成;每一个结点只有一个父结点但可以有多个子结点;但是有一个结点例外,该结点没有父结点,此结点称为根结点

  专业术语

 结点 / 父结点(只有一个) / 子结点 / 子孙 / 堂兄弟

 深度:从根结点到最底层结点的层数(根结点是第一层)

 叶子结点:没有子结点的结点

 非终端结点:实际就是非叶子结点,即有子结点的结点

 度:子结点的个数

2、分类

一般树:任意一个结点的子结点的个数都不受限制

二叉树:任意一个结点的子结点的个数最多两个,且子节点的位置不可更改

二叉树的分类:

一般二叉树

满二叉树:在不增加树的层数的情况下,无法再多添加一个结点的二叉树就是满二叉树

完全二叉树:如果只是删除满二叉树最底层最右边的连续若干个结点,这样形成的二叉树就是完全二叉树

满二叉树是完全二叉树的特例,完全二叉树包含满二叉树

森林:n个互不相交的树的集合

   3、存储(解决非线性结构用线性结构表示的问题)

二叉树的存储

连续存储[完全二叉树]

一般二叉树要以数组的方式存储,要先转化成完全二叉树,因为如果只存有效节点(无论以哪种规则:先序,中序,后序),则无              法知道这个树的原本样子。

优点:查找某个结点的父结点和子结点(也包括判断有没有子结点)速度很快

缺点:耗用内存空间过大

链式存储

一般树的存储

双亲表示法(求父结点方便)

孩子表示法(求子结点方便)

孩子双亲表示法(求父结点和子结点都方便)

二叉树表示法

                   把一棵普通树转化成二叉树来存储

                  具体转化方法:

                                   设法保证任意一个结点的左指针域指向它的第一个孩子,右指针域指向它的下一个兄弟

                                  一棵普通树转化为的二叉树一定没有右子树

森林的存储

                       先把森林转化为二叉树,再存储二叉树

                       具体方式为:根节点之间可以当成是兄弟来看待

操作

树的遍历

先序遍历[先访问根结点]

先访问根结点,再先序访问左子树,后先序访问右子树

中序遍历[中间访问根结点]

先中序遍历左子树,再访问根节点,后中序访问右子树

后序遍历[最后访问根结点]

先后续遍历左子树,再后续遍历右子树,后访问根结点

已知两种遍历求原始二叉树

通过 先序遍历和中序遍历 或者 中序遍历和后序遍历 我们可以还原出原始的二叉树,但是通过先序遍历和后序遍历是无法还原出原始的二叉树的。

换种说法:只有通过 先序遍历和中序遍历 或者 中序遍历和后序遍历 我们才可以唯一的确定一个二叉树的。

应用

树是数据库中数据组织的一种重要形式

操作系统子父进程的关系本身就是一棵树

面向对象语言中类的继承关系本身就是一棵树

赫夫曼树

  二、图

第五部分 模块三:查找和排序

  两者的关系:排序是查找的前提,排序是重点

  折半查找

  排序:(考虑三个问题:时间、空间、稳定性)

    冒泡

    插入

    选择

快速排序

归并排序

第五部分、代码(C语言)

1、数组

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>//包含了stdlib 

//定义了一个数据类型 该数据类型的名字叫做 struct Arr  该数据类型含有三个成员。
struct Arr
{
	int * pBase;//存储的是数组第一个元素的地址
	int len;//数组所能容纳的最大元素的个数
	int cnt;//当前数组有效元素的个数
	//int increment;//自动增长因子
	//这样的话就是每一次增长不是 一个一个的增长 而是一次增长一部分  

};
//函数声明的时候  末尾的分号是不能省得。
void init_arr(struct Arr * pArr,int length);//进行初始化
bool append_arr(struct Arr * pArr,int val);//追加 添加到末尾
bool insert_arr(struct Arr * pArr,int pos,int val);//pos的值从1 开始在 pso 位置的前面插入一个元素
bool  delete_arr(struct Arr * pArr,int pos,int *pVal);//用指针 返回删除的数据。

bool get();
bool is_empty(struct Arr * pArr);
bool is_full(struct Arr * pArr);
void sort_arr(struct Arr * pArr);
void show_arr(struct Arr * pArr);
void inversion_arr(struct Arr * pArr);



int main(void)
{
	struct Arr  arr;
	
	int val;

	init_arr(&arr,6);

	 show_arr(&arr);

/*	append_arr(&arr,1);
	append_arr(&arr,2);
	append_arr(&arr,3);
	append_arr(&arr,4);
	append_arr(&arr,5);
	
*/
	append_arr(&arr,1);
	append_arr(&arr,2);
	append_arr(&arr,3);
	append_arr(&arr,4);
	//insert_arr(&arr,5,99);
	show_arr(&arr);
/*	if(delete_arr(&arr, 3, &val))
	{
		printf("删除成功\n");
		printf("您删除的元素是:%d\n",val);

	}
	
	else{
		printf("删除失败");

	}
	*/
	inversion_arr(&arr);
	show_arr(&arr);
	sort_arr( &arr);
	show_arr(&arr);
	
	return 0;

}

void init_arr(struct Arr * pArr,int length)
{
	//(*pArr ).len = 99;
	pArr -> pBase = (int *) malloc(sizeof(int)*length);
	//如果内存已经用完 分配失败 返回NULL
	if(NULL == pArr ->pBase)
	{
		printf("动态内存分配失败!\n");
		exit(-1);//终止整个程序

	}
	else
	{
		pArr ->len = length;
		pArr->cnt = 0;
	}
	return;
}


bool is_empty(struct Arr * pArr)
{
	if(0 == pArr->cnt)
		return true;
	else
		return false;
}


bool is_full(struct Arr * pArr)
{
	if(pArr->cnt == pArr ->len)
	{
		return true;
	}
	else
		return false;
}


void show_arr(struct Arr * pArr)
{
//	if(数组为空)
//		提示用户数组为空
//	else
//	输出数组有效内容
	if(is_empty(pArr))
	{
		printf("数组内容为空\n");
	}
	else
	{	
		for(int i = 0;i<pArr->cnt;++i)
		{
			printf("%d\n",pArr->pBase[i]);
		}
		printf("\n");
	}
}


bool append_arr(struct Arr * pArr,int val)//追加 添加到末尾
{
	//满的时候返回false
	if(is_full(pArr))
		return false;
	//不满的时候添加
	pArr->pBase[pArr->cnt] = val;
	(pArr->cnt)++;

	return true;

}

//这里很重要  一定要自己画图 自己试试
bool insert_arr(struct Arr * pArr,int pos,int val)
{
	int  i;
	if(is_full(pArr))
		return false;
	if(pos<1 || pos>pArr->cnt+1)
	{
		return false;

	}
	for(i = pArr->cnt;i>= pos -1;--i)
	{
		pArr ->pBase[i+1] = pArr->pBase[i];
	
	}
		pArr->pBase[pos-1] = val;
		(pArr->cnt)++;
		return true;
}


bool  delete_arr(struct Arr * pArr,int pos,int *pVal)
{
	int i ;
	if(is_empty(pArr))
		return false;

	if(pos<1 || pos>pArr ->cnt)
		return false;
	*pVal = pArr->pBase[pos-1];
	for(i = pos;i<pArr->cnt;i++)
	{
		pArr->pBase[i-1] = pArr->pBase[i];
	}
	pArr->cnt --;
	return true;

}


void inversion_arr(struct Arr * pArr)
{

	int i = 0;
	int j = pArr ->cnt-1;
	int t;
	while(i<j)
	{
		t = pArr->pBase[i];
		pArr->pBase[i] = pArr ->pBase[j];
		pArr->pBase[j] = t;
		i++;
		j--;
	}

}


//这里讲的是冒泡排序
void sort_arr(struct Arr * pArr)
{
	int i ,j;
	int t;

	for(i = 0;i<pArr->cnt;++i)
	{
		for(j = 1;j<=pArr->cnt;++j)
		{

			if(pArr->pBase[j] <pArr->pBase[i])
			{
				t =pArr->pBase[i];
				pArr->pBase[i] = pArr->pBase[j];
				pArr->pBase[j] = t;
			}
		}
	}

}

2、链表

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


typedef struct Node
{
//数据域
	int data;
//指针域
	struct Node * pNext;

//指针域指向的 是与 本身结点数据类型相同的下一个结点 所以数据类型是 struct Node *;

}NODE,*PNODE;
//NODE 等价于 struct Node,
//PNODE 等价于 struct Node *;

//函数声明
PNODE  create_list(void);
void traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE);
bool insert_list(PNODE,int,int);
bool delete_list(PNODE,int,int *);
void sort_list(PNODE);


int main(void)

{
	PNODE pHead = NULL;//等价于 struct Node * pHead = NULL;

	//create_list()的功能:创建一个非循环单链表,并将该链表的头结点的地址赋给 pHead

	pHead = create_list();
	traverse_list(pHead);//遍历

	if(is_empty(pHead))
		printf("链表为空\n");
	else
		printf("链表不空\n");


	int len = length_list(pHead);

	printf("链表的长度是%d \n",len);

	sort_list(pHead);

	traverse_list(pHead);//遍历
	insert_list(pHead,2,33);
	traverse_list(pHead);//遍历
		
	int val;

	if(delete_list(pHead,4,&val))
	{
		printf("您删除的元素是%d\n",val);

	}
	else
	{
			printf("删除失败%d\n",val);
	}


	return 0;


}
//创建一个链表,创建一个非循环单链表,并将该链表的头结点的地址赋给 pHead

PNODE  create_list(void)
{
	//C语言里的写法是把所有的变量都定义在前面
	int len;//用来存放有效结点的个数
	int i;
	int val;//用来存放用户输入的有效结点的值


	//升成一个头结点  头结点的数据类型与 其他结点是相同的。
	//分配了一个不存放有效数据的头结点
	PNODE pHead = (PNODE)malloc(sizeof(NODE));

	//
	if(NULL == pHead)
	{
		printf("分配失败,程序终止。\n");
		exit(-1);

	}
	//pTail  永远指向 尾结点
	PNODE pTail = pHead;
	pTail -> pNext = NULL;

	printf("请输入您需要生成的链表的结点的个数:len =");
	scanf("%d",&len);

	for(i =0;i<len;++i)
	{
		printf("请输入第%d个结点的值:",i+1);
		scanf("%d",&val);

		PNODE pNew  = (PNODE)malloc(sizeof(NODE));

		if(NULL == pNew)
		{
			printf("分配失败,程序终止。\n");
			exit(-1);

		}

		pNew ->data = val;
		//新生成的结点要挂到整个链表的最后的一个位置。
		
		pTail -> pNext = pNew;
		pNew ->pNext = NULL;
		pTail = pNew;
	
	
	
	}

	return pHead;
	

}
//遍历这个链表
void traverse_list(PNODE pHead)
{
//p 指向的是链表的第一个有效结点
	PNODE p  = pHead->pNext;

	while(p!= NULL)
	{
		printf("  %d",p->data);
		p = p->pNext;

	}
	printf("\n");


	return;

}
bool is_empty(PNODE pHead)
{
	if(pHead->pNext == NULL)
		return true;
	else
		return false;

}
int length_list(PNODE pHead)
{
	PNODE p = pHead ->  pNext;
	int len = 0;

	while(p != NULL)
	{

		len++;
		p = p->pNext;
	}
	return len;

}


void sort_list(PNODE pHead)
{	//实际上算法都是类似

	//数组和链表同样都是 线性结构
	int i,j,t;
	PNODE p, q;
	int len = length_list(pHead);

	//i 是数组中第一个有效元素的下标  p 是链表中第一个有效元素的 地址。

	for(i = 0,p = pHead ->pNext;i<len - 1;++i,p = p->pNext)
	{
		//  q 就应该是p 的下一个元素
			for(j = i+1,q = p ->pNext;j<len;++j,q = q->pNext)
			{
				/*
					请参照数组中的方式来理解 链表
					if(a[i] > a[j])
					{
						t = a[i];
						a[i] = a[j];
						a[j] = t;
					
					}
				*/
				
				if((p->data )> (q->data))
				{
					t = p->data;
					p->data = q->data;

					q->data= t;
				
				}


			}
	}

	return;

}
//在pHead 所指向链表的第pos 个结点的前面插入一个新的结点,该结点的值是 val并且 pos 的值是从1 开始。
bool insert_list(PNODE pHead,int pos,int val)
{

	int i = 0;
	PNODE  p = pHead;
	while(p != NULL && i<pos -1)
	{
		p = p->pNext;
		i++;


	}
	if(i>pos-1||p== NULL)
		return false;

	PNODE pNew = (PNODE)malloc(sizeof(PNODE));
	if(pNew == NULL)
	{
		printf("动态分配内存失败");
		exit(-1);
		
	}
	
	pNew ->data = val;
	//定义了一个临时结点
	PNODE q = p->pNext;
	p->pNext = pNew;
	pNew ->pNext = q;

	
	return true;


}

bool delete_list(PNODE pHead,int pos,int * pVal)
{
	
	int i = 0;
	PNODE  p = pHead;
	while(p ->pNext!= NULL && i<pos -1)
	{
		p = p->pNext;
		i++;


	}
	if(i>pos-1||p ->pNext== NULL)
		return false;

	PNODE q = p ->pNext;
	*pVal = q ->pNext;

	//删除p结点后面的结点
	p->pNext = p->pNext->pNext;
	free(q);
	q = NULL;	
	
	return true;

}

3、栈(链表结构)

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct Node
{
	int data;
	struct Node * pNext;

}NODE,* PNODE;

typedef struct Stack
{
	PNODE pTop;
	PNODE pBottom;


}STACK,* PSTACK;

void init(PSTACK);//PSTACK  等价于struct STACK *
void push(PSTACK,int);
void traverse(PSTACK);
bool pop(PSTACK,int *);
void clear(PSTACK);

int main(void)
{
	int val;//用来保存出栈的元素

	//这句话一执行,内存中就会有一个变量,这个变量具有pTop和pBottom两个成员。
	//但是这里还没有数据因为还没有对他进行初始化。
	STACK S;//STACK  等价于  struct Stack 
	
	init(&S);//对栈进行初始化  目的是造出一个空栈
	push(&S,1);//压栈
	push(&S,2);
	push(&S,3);
	push(&S,4);
	push(&S,5);
	push(&S,6);
	push(&S,7);
	traverse(&S);

	if(pop(&S,&val))
	{
		printf("出栈成功,出栈的元素是:%d\n",val);

	}
	else
	{	

		printf("出栈失败");

	}

	traverse(&S);//遍历

	clear(&S);

	traverse(&S);

	return 0;

}
void init(PSTACK pS)
{
	pS->pTop = (PNODE)malloc(sizeof(NODE));
	if(pS->pTop ==NULL)
	{
		printf("内存分配失败");

		exit(-1);	
	}
	else
	{

		pS->pBottom  = pS->pTop;
		pS->pTop->pNext = NULL;//pS->pBottom->pNext = NULL;  这两者是等价的。		

	}	
	
}
void push(PSTACK pS,int val)
{
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	pNew ->data = val;
	pNew ->pNext = pS->pTop;//pS->pTop 不能改成pS->pBottom.
	pS->pTop = pNew;

	return;

}
void traverse(PSTACK pS)
{

	//定义一个指针p永远指向最顶层的元素  或者说 指向的是 遍历到的最顶上的元素。
	PNODE p = pS->pTop;

	while(p != pS->pBottom)
	{
		printf("   %d",p->data);
		p = p->pNext;

	}
	printf("\n");

	return;

}
bool empty(PSTACK pS)
{
	if(pS->pTop == pS->pBottom)
		return true;
	else
		return false;

}
//把pS 所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,
//如果出栈失败,返回false,否则返回true。
bool pop(PSTACK pS,int * pVal)
{

	if(empty(pS))//pS  本身存放的就是 s 的地址。
	{
		return false;

	}else
	{
		//先让pTop 指向栈顶的下一个元素 然后再将r所指向的栈顶元素的内存释放掉。
		
		PNODE r = pS->pTop;
		
		pS->pTop = r ->pNext;

		*pVal = r->data;

		free(r);
		r = NULL;

		return true;
	}

}
void clear(PSTACK pS)
{
	if(empty(pS))
		return;
	else
	{
		PNODE p = pS->pTop;
		PNODE q = p->pNext;
		while(p != pS->pBottom)
		{
			q = p->pNext;
			free(p);
			p = q;

		}
		pS->pTop = pS->pBottom;
	}
}

4.队列(循环数组)

#include<stdio.h>
#include<malloc.h>
typedef struct Queue
{
	int * pBase;
	int front;
	int rear;
}QUEUE;
void init(QUEUE *);
bool en_queue(QUEUE	*,int);
void traverse_queue(QUEUE *);
bool full_queue(QUEUE *);
bool out_queue(QUEUE *,int *);
bool empty_queue(QUEUE *);
int main(void)
{
	QUEUE Q;
	init(&Q);
	int val;
	en_queue(&Q,1);
	en_queue(&Q,2);
	en_queue(&Q,3);
	en_queue(&Q,4);
	en_queue(&Q,5);
	en_queue(&Q,6);
	en_queue(&Q,7);

	traverse_queue(&Q);
	if(out_queue(&Q,&val))
	{
		printf("出队成功,出队元素是  %d\n",val);
	}
	else
	{	
		printf("出队失败\n");
	}
	traverse_queue(&Q);
	return 0;

}
//对队列进行初始化
void init(QUEUE * pQ)
{
	pQ->pBase = (int *)malloc(sizeof(int)* 6);
	pQ->front = 0;
	pQ->rear = 0;
}
bool en_queue(QUEUE	*pQ,int val)
{
	if(full_queue(pQ))
		return false;
	else
	{
		pQ->pBase[pQ->rear]  =val;  //pQ-> pBase 代表的是数组  因为这个循环队列是用数组来实现的。

		pQ->rear = (pQ->rear +1 )%6;
		return true;
	}
}
bool full_queue(QUEUE * pQ)
{
	if( (pQ->rear +1)%6 == pQ->front)
		return true;
	else
		return false;
}

void traverse_queue(QUEUE * pQ)
{
	int i = pQ->front;
	while(i != pQ->rear)
	{
		printf("  %d",pQ->pBase[i]);
		i = (i+1)%6;
			
	}
	printf("\n");
	return;
}
bool empty_queue(QUEUE *pQ)
{
	if(pQ->front == pQ->rear)
		return true;
	else
		return false;
}
bool out_queue(QUEUE * pQ,int * pVal)
{
		if(empty_queue(pQ))
		return false;
	else
	{
		* pVal = pQ->pBase[pQ->front];
		pQ->front = (pQ->front +1)%6;
		return true;
	}

}

5.递归(汉诺塔)

#include<stdio.h>
void hannuota(int n,char A,char B,char C)
{
	//如果是一个盘子
	//		直接将A柱子上的盘子  从A 移到 C;
	//否则
	//		先将A柱子上的n-1盘子借助C移到B
	//		将A柱子上的第n个盘子移到C
	//		最后将B柱子上的n-1个盘子借助A移到C
	
	if(n ==1)
	{
		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n",n,A,C);

	}
	else
	{
		hannuota(n-1,A,C,B);
		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n",n,A,C);
		hannuota(n-1,B,A,C);

	}

}

int main(void)
{
	//定义三个柱子
	char ch1 = 'A';
	char ch2 = 'B';
	char ch3 = 'C';

	int n;
	printf("请输入要移动盘子的个数\n");
	scanf("%d",&n);
	hannuota(n,ch1,ch2,ch3);

	return 0;

}

6、链式二叉树

#include<stdio.h>
#include<malloc.h>

struct BTNode
{
	int data;
	struct BTNode * pLchild;//  p 是指针  L 是左 child 是孩子
	struct BTNode * pRchild;

};
struct BTNode * CreateBTree(void);
void 	PreTraverseBTree(struct BTNode *);
void 	InTraverseBTree(struct BTNode *);
void 	PostTraverseBTree(struct BTNode *);

int main(void)
{
	struct BTNode  * pT = CreateBTree();

	PreTraverseBTree(pT);
	InTraverseBTree(pT);
	PostTraverseBTree(pT);

	return 0;
}
void 	PreTraverseBTree( struct BTNode * pT)
{
	//先访问根节点
	//再先序访问左子树
	//再先序访问右子树
	if(pT!=NULL)
	{
		printf("%c\n",pT->data);
		if(pT!=NULL)
		{
				//pT->pLchild可以代表整个左子树
				PreTraverseBTree(pT->pLchild);
		}
		if(pT!=NULL)
		{
				PreTraverseBTree(pT->pRchild);
		}
	}
}

void 	InTraverseBTree(struct BTNode * pT)
{
/*	中序遍历左子树
    访问根节点
	中序遍历右子树
	*/
	if(pT!=NULL)
	{
		
		if(pT!=NULL)
		{
				//pT->pLchild可以代表整个左子树
				InTraverseBTree(pT->pLchild);
		}
		printf("%c\n",pT->data);
		if(pT!=NULL)
		{
				InTraverseBTree(pT->pRchild);
		}
	}
}
void 	PostTraverseBTree(struct BTNode * pT)
{
/*	后序遍历左子树
  后续遍历右子树
  访问根节点。
*/
	if(pT!=NULL)
	{
		
		if(pT!=NULL)
		{
				//pT->pLchild可以代表整个左子树
				PostTraverseBTree(pT->pLchild);
		}
	
		if(pT!=NULL)
		{
				PostTraverseBTree(pT->pRchild);
		}
			printf("%c\n",pT->data);

	}
}

//静态的构造一个二叉树

struct BTNode * CreateBTree(void)
{

	struct BTNode  * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
	struct BTNode  * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
	struct BTNode  * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
	struct BTNode  * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
	struct BTNode  * pE = (struct BTNode *)malloc(sizeof(struct BTNode));
	pA->data = 'A';
	pB->data = 'B';
	pC->data = 'C';
	pD->data = 'D';
	pE->data = 'E';

	pA->pLchild =pB;
	pA->pRchild = pC;
	pB->pLchild = pB->pRchild =NULL;
	pC->pLchild = pD;
	pC->pRchild = NULL;
	pD->pLchild = NULL;
	pD->pRchild = pE;
	pE->pLchild = pE->pRchild = NULL;

	return pA;

}

7、快速排序

#include<stdio.h>

void QuickSort(int *,int ,int  );
int FindPos(int *,int ,int);
int main(void)
{
	int a[6] = {2,1,0,5,4,3};
	int i;
	QuickSort(a,0,5);

	//第二个参数表示第一个元素的下标
	//第三个参数表示最后一个元素的下标。
	for(i =0;i<6;i++)
	{	
		printf("%d  ",a[i]);
	}
	printf("\n");


}
void QuickSort(int *a,int low,int high)
{	
	int pos;
	if(low < high)
	{	
		pos = FindPos(a,low,high);
		QuickSort(a,low,pos-1);
		QuickSort(a,pos+1,high);


		
	}

}
int FindPos(int *a,int low ,int high)
{
	int val = a[low];
	while(low<high)
	{
		while(low<high && a[high]>=val)
		{
			--high;


		}
		a[low] = a[high];
		while(low<high  && a[low]<=val)
			++low;
		a[high] = a[low];

	}
	//终止while循环之后low和while一定是相等的。

	a[low] =val;
	return	high;//high可以改为low但不能改为啊val 也不能改为 a[low] 和a[high]

}

                                                                                                                                    ——参考郝斌老师“数据结构”视屏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!关于学习数据结构的C语言笔记,我可以给你一些基本的指导和概念。数据结构是计算机科学中非常重要的一门课程,它涉及存储和组织数据的方法。C语言是一种常用的编程语言,很适合用于实现各种数据结构。 下面是一些数据结构的基本概念,你可以在学习笔记中包含它们: 1. 数组(Array):一种线性数据结构,可以存储相同类型的元素。在C语言中,数组是通过索引访问的。 2. 链表(Linked List):也是一种线性数据结构,但不需要连续的内存空间。链表由节点组成,每个节点包含数据和指向下一个节点的指针。 3. 栈(Stack):一种后进出(LIFO)的数据结构,类似于装满物品的箱子。在C语言中,可以使用数组或链表来实现栈。 4. 队列(Queue):一种出(FIFO)的数据结构,类似于排队等候的队伍。同样可以使用数组或链表来实现队列。 5. 树(Tree):一种非线性数据结构,由节点和边组成。每个节点可以有多个子节点。二叉树是一种特殊的树结构,每个节点最多有两个子节点。 6. 图(Graph):另一种非线性数据结构,由节点和边组成。图可以用来表示各种实际问题,如社交网络和地图。 这只是数据结构中的一些基本概念,还有其他更高级的数据结构,如堆、哈希表和二叉搜索树等。在学习笔记中,你可以介绍每个数据结构的定义、操作以及适合使用它们的场景。 希望这些信息对你有所帮助!如果你有任何进一步的问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值