数据结构

目录

数据结构

当遇到一个实际问题时候,首先需要解决两件事:

​ 1.如何将数据存储到计算机中

​ 2.用什么方法和策略解决问题

前者是数据结构,后者是算法。

数据是一切能输入计算机中的信息的总和,结构是指数据之间的关系。

数据结构就是将数据及其之间的关系有效地存储在计算机中并进行基本操作。

算法是对特定问题求解步骤的一种描述,通俗讲就是解决问题的方法和策略。

这就是Niklaus Wirth教授所说的:“数据结构+算法=程序”

一、基础知识

[1] 数据一些概念
  1. 数据:数据是指所有能输入到计算机中的描述客观事物的符号,包括文本、声音、图像、符号等。

  2. 数据元素:数据元素是数据的基本单位,也称节点或记录。

  3. 数据项:数据项表示有独立含义的数据最小单位,也称域。若干个数据项构成一个数据元素,数据项是不可分割的最小单位

    数据结构

  4. 数据对象:数据对象是指相同特性的数据元素的集合,是数据的一个子集。

  5. 数据结构:数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构研究问题将带有关系的数据存储到计算机中,并进行相关操作。

[2] 三要素

逻辑结构、存储结构、运算

数据结构

(1)逻辑结构

逻辑结构是数据元素之间的关系,与数据存储无关,独立于计算机

  1. 线性结构
    • 一个接着一个:项链,手表,烧烤,队列等。
  2. 树形结构
    • 一对多,像家谱,树根。
  3. 图形结构
    • 多对多,地图、渔网
(2)存储结构
  1. 顺序存储
    • 内存中连续存储
  2. 链式存储
    • 逻辑上是连续的,但是在内存中位置不一定连续
  3. 散列存储
    • 又叫哈希存储,由节点的关键码值key决定节点的存储地址,用散列函数确定数据元素的存储位置与关键码之间的对应关系。
  4. 索引存储
    • 索引存储除了建立节点信息之外,还建立索引表来标识节点的地址。索引表由若干索引项组成。
(3) 运算

初始化、查找、取值、插入、删除、遍历等等

[3] 算法
  1. 算法概念
    • 算法是指对特定问题求解步骤的一种描述
    • 算法不依赖于编程语言
  2. 算法的特点
    • 有穷性:算法是由若干条指令组成的有穷序列,总是在执行若干次后结束,不能永不停止。
    • 确定性:每条语句都有确定的含义,无歧义。
    • 可行性:算法在当前环境条件下可以通过有限次运算实现。
    • 输入和输出:有0个或多个输入,一个或多个输出。
  3. 好算法的标准
    • 正确性:指算法能够满足具体问题的需求,程序运行正常,无语法错误。
    • 易读性:算法遵循标识命名规则,简洁易懂,注释恰当。
    • 健壮性:算法对非法数据以及操作有较好的反应和处理。
    • 高效性
      • 指算法运行效率高,即算法运行消耗时间短。
      • 因为计算机配置高低不齐,不能单纯用秒数来衡量。所以用执行的次数来衡量,因此将算法基本运算执行次数作为时间复杂度的度量标准。
    • 低存储性
      • 指算法所需要的存储空间低
      • 算法占用的空间大小称为空间复杂度。
[4] 算法的复杂度
  1. 时间复杂度:一般将算法基本运算的次数作为时间复杂度的度量标准

  2. 空间复杂度:

    • 算法占用的空间:
      • 算法本身所占空间
      • 输入输出数据所占空间
      • 额外需要的辅助空间
    • 输入输出数据所占空间是必须的
    • 算法本身所占空间,可以通过精简算法来缩减,这个压缩量很小,可以忽略
    • 在运行时候使用的辅助变量所占用的空间,即辅助空间是衡量空间复杂度的关键因素。
  3. 常见的时间复杂度

    • 常数阶:常数阶算法运行次数是一个具体的常数
    • 多项式阶:很多算法时间复杂度是多项式,通常O(n)\O(n2 )\O(n3)
    • 对数阶:对数阶时间复杂度运行效率较高
    • 指数阶:
      • 如果时间复杂度是O(2n),那就容易死机
      • 指数阶时间复杂度运行效率极低
      • 常见O(2^n) O(n!) O(n^n)

O(1)<O(log2n)<O(n)<O(n2) <O (n3) <O(2n) <O(n!) <O(nn)

二、线性表

[1] 线性表
  1. 线性表特点:
    • 线性表是由n(n>0)个同类型的数据元素组成的有限序列,他是最基本的、最常用的一种线性结构。
    • 有唯一的开始和结束,不分叉。非循环表,第一个元素没有前驱,最后一个元素没有后继,中间元素有唯一的前驱和唯一的后继。
    • 顺序表 = 线性结构+顺序存储
    • 改查方便
  2. 代码如下
头文件
#include <datas.h>
#define N 24  //顺序表最大元素的个数

typedef struct sqlist { //定义顺序表结构体
	int data[N];
	int last;  //last初始化为0就是顺序表元素的个数,初始化为-1就是最大元素数组下标
}sqlist_t;

sqlist_t * list_creat(void);  //创建顺序表
int list_exit(sqlist_t *);  //判断sqlist_t类型的指针是否为NULL
int list_clear(sqlist_t *);   //清空顺序表
int list_empty(sqlist_t *);  //判空
int list_full(sqlist_t *);  //判满
int list_insert(sqlist_t *, int, int);  //按位置插入
int list_loc_find(sqlist_t *, int);  //按位置查找,返回值
int list_value_find(sqlist_t *, int, char);  //按值查找,返回位置
int list_delete(sqlist_t *, int);  //按照位置删除元素
int list_repeat(sqlist_t *);  //去掉重复元素
int list_show(sqlist_t *);  //遍历展示
int list_free(sqlist_t **);  //释放顺序表
int list_joint(sqlist_t * pt1, sqlist_t * pt2) //合并两个表
int list_sort(sqlist_t * pt)  //排序
    
func.c
#include "head.h"

int list_exit(sqlist_t * pt)   //判断pt指针是否为为空
{/*{{{*/
	if (NULL == pt)
		FPRINT_E("error: pt is NULL \n");
	else
		return 0;
}/*}}}*/

sqlist_t *list_creat(void)    //创建线性表
{/*{{{*/
	sqlist_t * pt = NULL;
	pt = (sqlist_t *)malloc(sizeof(sqlist_t));
	if (NULL == pt)
		FPRINT_E("creat list error\n");
	memset(pt, 0, sizeof(sqlist_t));  //初始化
	pt->last = 0;  //last初始化为0,那么last就是顺序表元素个数.如果初始化为-1,表示元素下标

	return pt;
}/*}}}*/

int list_clear(sqlist_t * pt)  //清空顺序表
{/*{{{*/
	list_exit(pt);  //先判断pt是否为NULL

	memset(pt, 0, sizeof(sqlist_t));  //初始化data
	pt->last = 0;   //清空last

	return 0;
}/*}}}*/

int list_empty(sqlist_t * pt);  //判空
{
  	list_exit(pt); //先判断pt是否为NULL
    return (pt->last == 0 ? 1 : 0);  //因为初始化的时候last为0,所以当last = 0表示顺序表中没有元素
}
int list_full(sqlist_t * pt);  //判满
{
    list_exit(pt); //先判断pt是否为NULL
    return (N == pt->last ? 1 : 0);  //当这个条件成立的时候,说明顺序表已满
}

int list_insert(sqlist_t * pt, int place, int value)  //按照位置插入值
{/*{{{*/
    list_exit(pt);  //先判断pt是否为NULL
	if (pt->last >= N)   //如果顺序表满了,那么此时无法插入
		FPRINT_E("list full.\n");
	if (place < 0 || place > pt->last)  //如果插入的位置小于0,或者大于last说明插入位置非法(这里可以等于last)
		FPRINT_E("insert position error.\n");

	int i;
    //按位置插入但是又不能打破顺序,所以从最后一个元素开始到place位置的所有元素都往后挪一位
	for(i = pt->last - 1; i >= place; i--)
	{
		pt->data[i + 1] = pt->data[i];  //吧i元素对应的数据往后挪
	}

	pt->data[place] = value;  //如果把要插入的值放到place位置上
	pt->last++; //然后别忘了把last+1,因为last是顺序表元素的个数

	return 0;
}/*}}}*/

int list_loc_find(sqlist_t * pt, int place)  //按照位置查找
{/*{{{*/
    list_exit(pt);  //先判断pt是否为NULL
    if (place < 0 || place >= pt->last)  //如果查找的位置小于0或者大于或等于last
        FPRINT_E("find place error.\n");  //说明插入位置非法
	int export;
	export = pt->data[place];  //输入的位置place就是顺序表数组下标

	return export;
}/*}}}*/

int list_value_find(sqlist_t * pt, int value, char cell[])  
{/*{{{*/   //按照值查找,因为查找到的元素不止一个,我们用一个cell数组来其下标,cell数组大小为N
    memset(cell, -1, N); //初始化cell全为-1,因为数组下标最小为0,这里注意不能写成sizeof(cell)
	int i, j, flags = 1;
	for(i = 0, j = 0; i < pt->last; i++)
	{
		if (pt->data[i] == value)  //相等的时候就找到了
		{
			debug("value: data[%d] = %d, value = %d\n", i, pt->data[i], value);
            cell[j++] = i;
			flags = 0;  //找到后置为0
		}
	}
	if (flags == 0)  //等于0说明找到了
		return 0;
	else
	{
		puts("not find.");
		return 1;
	}
}/*}}}*/

int list_delete(sqlist_t * pt, int place)  //按位置删除元素
{/*{{{*/
	int i;
	
    list_exit(pt); //先判断pt是否为NULL
	if (place >= pt->last || place < 0) //和前面一样,判断位置是否合法
		FPRINT_E("enter place error. \n");
	//只需要把从place后面的每一位都向前移动一位即可
	for (i = place; i < pt->last - 1; i++) 
		pt->data[i] = pt->data[i + 1];  
	pt->last--;  //去掉一个元素后别忘了last-1

	return 0;
}/*}}}*/

int list_repeat(sqlist_t * pt)  //去除重复元素
{/*{{{*/
    list_exit(pt);  //先判断pt是否为NULL
	int i, j;	
	//用冒泡方法进行遍历,如果有相等的元素就删除后面的那个元素,因为删除后面元素操作元素要少一些
	for(i = 0; i < pt->last-1; i++)
		for(j = i+1; j < pt->last; j++)
			if (pt->data[i] == pt->data[j]) //如果相等
            {
				list_delete(pt, j);  //删除后面的元素
                j--;  
                //这里j必须自减,因为顺序表和我们普通数组不一样,
                //刚刚我们调用了list_delete函数,他会把后面的数往前挪,而此时j又会自加
                //导致每次执行list_delete函数后,下次我们遍历的是下下个元素,不是下一个
                //少遍历了一次
            }
				

	return 0;
}/*}}}*/

int list_show(sqlist_t * pt)  //遍历展示
{/*{{{*/
	list_exit(pt);  //先判断pt是否为NULL
	int i;
    
	for (i = 0; i < pt->last; i++)
		printf("data[%d] = %d\n", i, pt->data[i]);
    
	return 0;
}/*}}}*/

int list_free(sqlist_t ** pt)  
{/*{{{*/     //释放顺序表,这里要传二级指针,不然无法将main中的pt指向NULL
	list_exit(*pt);  //先判断pt是否为NULL
	free(*pt);	  //释放对应的内存
	*pt = NULL;    //将主函数传到该函数的那个指针指向NULL

	return 0;
}/*}}}*/

int list_joint(sqlist_t * pt1, sqlist_t ** pt2)  //两个表拼接
{/*{{{*/
	int i, j, ret;

	list_exit(pt1);  //先判断pt1是否为NULL
	list_exit(*pt2);
	if (pt1->last + (*pt2)->last > N)  //这里要判断两个表拼接在一起是否会超过顺序表最大值N
		FPRINT_E("cell too much, not joint\n");
/*  这里用的是先去重然后再合并,在合并的同时再去重,如果是这种方法必须先都去重一遍然后在开始合并
	list_repeat(pt1);  
	list_repeat(*pt2);
	for(i = 0; i < (*pt2)->last; i++)
	{
		ret = list_value_find(pt1, (*pt2)->data[i]);
		if (ret == 1)
			list_insert(pt1, pt1->last, (*pt2)->data[i]);
	}
*/ 
    //最好的方法就是先都合并,然后再整体去重
    for(i = 0; i < (*pt2)->last; i++) //循环遍历pt2顺序表,然后每一次都插入到pt1顺序表的末尾
        list_insert(pt1, pt1->last, (*pt2)->data[i]);
    list_repeat(pt1);  //然后去重
    free(*pt2);  //别忘记释放掉pt2
    *pt = NULL  

	return 0;
}	/*}}}*/

int list_sort(sqlist_t * pt)  //排序
{
    list_exit(pt);  //先判断pt1是否为NULL
    
    int t;
	for(i = 0; i < pt->last-1; i++)
		for (j = i + 1; j < pt->last; j++)
			if (pt->data[i] > pt->data[j])  //从小到大排序
			{
				t = pt->data[i];
				pt->data[i] = pt1->data[j];
				pt->data[j] = t;
			}
}
main.c
#include <datas.h>

int main(int argc, const char *argv[])
{/*{{{*/
	sqlist_t * pt1 = list_creat();
	sqlist_t * pt2 = list_creat();
	int place, value, ret, i = 0;

	printf("Please enter the value you want to insert\n");
	printf("enter q to quit > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		list_insert(pt1, i++, value);
		//list_value_find(pt1, value);
		//list_loc_find(pt1, 1);
		printf("Please enter next > ");
		fflush(stdout);
	}
	getchar();
	list_show(pt1);
#if 0
	printf("insert\n");
	list_insert(pt1, 5, 250);
	list_show(pt1);
#endif

#if 0
	printf("repeat\n");
	list_repeat(pt1);
	list_show(pt1);
#endif
	
#if 0
	int cell[N] = {0};
	i = 0;
	printf("value find\n");
	list_value_find(pt1, 10, cell);
	while (*(cell + i) != -1)
		printf("index: %d\n", cell[i++]);
	list_show(pt1);
#endif

#if 0
	printf("local find\n");
	list_loc_find(pt1, 3);
	list_show(pt1);
#endif

#if 0
	printf("delete\n");
	list_delete(pt1, 3);
	list_show(pt1);
#endif

#if 0
	//joint
	i = 0;
	printf("Please enter the value you want to insert\n");
	printf("enter q to quit > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		list_insert(pt2, i++, value);
		//list_value_find(pt1, value);
		//list_loc_find(pt1, 1);
		printf("Please enter next > ");
		fflush(stdout);
	}
	list_show(pt1);
	printf("joint\n");
	list_joint(pt1, pt2);
	list_show(pt1);
#endif

#if 0
	printf("clear \n");
	list_clear(pt1);
	list_show(pt1);
#endif

	list_free(&pt1);

    return 0;
}/*}}}*/

三、链表

[1] 链表概念
  1. 链表 = 线性结构+链式存储
  2. 分为两类
    • 无头节点:
      • 头部插入和其他位置插入写法不一样,操作时候需要判断是否为头插
      • 头部删除和其他位置删除写法不一样,操作时候需要判断是否为头删
      • 处理空表和处理非空表方式不一样
      • 而且空表和不存在效果也是一样的
    • 有头节点
      • 头指针L指向头节点
      • 插入删除操作写法一样,容易区分链表空和销毁
      • 防止单链表是空而设置,当链表为空时候,有头节点链表头指针指向头节点,头节点指针域数值为NULL,无头节点单链表头指针指向NULL
[2] 实现
head.h
#include <datas.h>


typedef struct node {
	int data;
	struct node * next;
}list_node_t;

//create
list_node_t *list_create(void);  //创建链表

//free list
int list_free(list_node_t **);  //释放链表

//head insert
int list_head_insert(list_node_t *, int); //按头插入链表

//list tarverse   //遍历
int list_show(list_node_t *);   

//tail insert   //尾插链表
int list_tail_insert(list_node_t *, int);

//get pot   //按位置查找对应节点
list_node_t *list_get_local(list_node_t *, int);

//local insert  //按位置插入
int list_place_insert(list_node_t *, int, int);  

//list_place_delete   //按位置删除
int list_place_delete(list_node_t *, int);

//get 
//sort   //排序
int list_sort(list_node_t *);

//reverse  //反转
int list_reverse(list_node_t *);

//list_merge  //合并
int list_merge(list_node_t *, list_node_t **);
func.c
#include "head.h"

//list node create
list_node_t *list_create(void)  //创建链表
{/*{{{*/
	list_node_t * pt = (list_node_t *) malloc(sizeof(list_node_t));  //创建节点
	if (NULL == pt)
		FPRINT_E("malloc error\n");
	//初始化节点
	pt->data = -1;  
	pt->next = NULL;

	return pt;
}/*}}}*/

//list node exit
void list_exit(list_node_t * pt)  //判断链表指针是否为NULL
{/*{{{*/
	if (pt == NULL)
		FPRINT_E("error: point is NULL\n");
}/*}}}*/

//list node free
int list_free(list_node_t ** pptr)  //释放链表
{/*{{{*/
	list_exit(*pptr);  //先判断是否为NULL
	//从头开始释放,定义两个变量,一个用于定位一个用于释放
	list_node_t * p;	
	list_node_t * q = *pptr;
	while (q != NULL)  
	{
		p = q;
		q = q->next;
        free(p);
	}
	*pptr = NULL;

	return 0;
}/*}}}*/

//lsit node head insert
int list_head_insert(list_node_t * pt, int value)  //按头插入链表
{/*{{{*/
	list_exit(pt);
		
	list_node_t * p = (list_node_t *) malloc(sizeof(list_node_t));
    if (NULL == p)  //申请内存失败
		FPRINT_E("error: malloc fail\n"); 

	p->data = value;  //更新值
	p->next = pt->next;
	pt->next = p;

	return 0;
}/*}}}*/

//list node traverse
int list_show(list_node_t * pt)
{/*{{{*/
	list_exit(pt);
	list_node_t * p = pt->next;  //定义一个变量

	int i = 0;
	while (p != NULL)  //当p指向最后一个元素的指针域时才退出
	{
		printf("data[%d] = %d\n", i++, p->data);
		p = p->next;
	}

	return 0;
}/*}}}*/       

//list node tail insert
int list_tail_insert(list_node_t * pt, int value)  //尾部插入
{/*{{{*/
	list_exit(pt);
	list_node_t * p;
	list_node_t * q = pt;
	p = list_create();  //此时p已经初始化好了
	p->data = value; //把值置为我们想要的

	while (q->next != NULL) //知道q指向的是链表最后一个元素
		q = q->next;
	q->next = p;  //此时再让q的指针域指向p

	return 0;
}/*}}}*/

// find place return node
list_node_t *list_get_local(list_node_t * pt, int place)
{/*{{{*/
	list_exit(pt);

	if (place < -1)
		FPRINT_E("intsert place < -1\n");
	else if (place == -1)  //输入-1 表示需要头节点
		return pt;

	list_node_t * p = pt;
	int i;
	for (i = 0; i <= place; i++) //这里当i大于Place的时候退出循环
	{
		p = p->next;
		if (p == NULL)  //如果p还没到place节点的时候链表就结束了
			FPRINT_E("inser place too big\n"); //说明查找的位置太大了
	}
	return p;
}/*}}}*/

//place insert
int list_place_insert(list_node_t * pt, int value, int place)
{/*{{{*/
	list_exit(pt);  //判断pt 是否为NULL
	if (place < 0)
		FPRINT_E("error: insert place < 0\n");
	list_node_t * q = list_get_local(pt, place - 1);  //获取插入位置的前一个节点地址

	list_node_t *p = (list_node_t *) malloc(sizeof(list_node_t)); //创建一个节点
	if (NULL == p)
		FPRINT_R("Application memory fail.\n", -1);

	p->data = value;  //把节点的值初始化成我们输入的值
	p->next = q->next;  //然后做一个简单的插入
	q->next = p;

	return 0;
}/*}}}*/

//place delete
int list_place_delete(list_node_t *pt, int place)
{/*{{{*/
	list_exit(pt);
	list_node_t *p, *q;
	p = list_get_local(pt, place - 1); //还是先找前面的那个元素
	if (NULL == p->next)  //如果前面的那个元素就是链表最后一个元素了,说明place超出了链表长度
		FPRINT_E("place too big, can't delete\n");
	//如果正常情况下
	q = p->next;
	p->next = q->next;
	free(q);  //释放
	q = NULL;

	return 0;
}/*}}}*/

//list node sort
int list_sort(list_node_t * pt)  //排序
{/*{{{*/
	list_exit(pt);
	int temp;
	list_node_t * p, * q;
	//用冒泡法来遍历,注意这里跳出循环的条件
	for (p = pt->next; p->next != NULL; p = p->next)	
		for (q = p->next; q != NULL; q = q->next)
			if (p->data > q->data)
			{
				temp = p->data;
				p->data = q->data;
				q->data = temp;
			}

	return 0;
}/*}}}*/

//list node reverser
int list_reverse(list_node_t *pt)  //翻转,相当于把后面的元素都进行头插
{/*{{{*/
	list_exit(pt);
    if (NULL == pt->next || NULL == pt->next->next)  //如果只有头节点,那么不需要操作直接退出
        return 0;
	list_node_t * p, * q;

	p = pt->next->next;  //p为要移动的元素
    q = p;  //这样如果只有一个元素节点,他就进不去while循环
	pt->next->next = NULL;  //最开始的0号元素肯定是最后一个,所以置为NULL
	while(q != NULL)
	{
		p = q;   //定位
		q = p->next;  //让p往前移动,定位
		p->next = pt->next;
		pt->next = p;
	}

	return 0;
}/*}}}*/

list node merge
int list_merge(list_node_t *pt1, list_node_t **pt2)  //合并链表
{/*{{{*/
	list_exit(pt1);
	list_exit(*pt2);
	list_node_t *p = pt1;

	while (p->next != NULL)    //跳出循环时 p->next 为NULL,到了链表1的结尾
		p = p->next;
	p->next = (*pt2)->next;  //此时把结尾指针指向链表2的第0号元素
	free(*pt2);  //释放pt2
	*pt2 = NULL;

	return 0;
}/*}}}*/
main
#include "head.h"

int main(int argc, const char *argv[])
{/*{{{*/
	list_node_t * list1 = list_create();

	int value, i = 0;
	printf("Please enter insert value \n");
	printf("enter q to quit > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		list_tail_insert(list1, value);
		printf("Please enter next > ");
		fflush(stdout);
	}
	list_show(list1);
	getchar();

#if 0
	list_node_t *p;
	printf("list_local_node");
	p = list_get_local(list1, 3);
	printf("p->data = %d\n", p->data);
#endif

#if 0
	printf("list_place_inset\n");
	list_place_insert(list1, 3, -1000);
	list_show(list1);
#endif

#if 0
	printf("list_place_delete\n");
	list_place_delete(list1, 0);
	list_show(list1);
#endif

#if 0
	printf("sort\n");
	list_sort(list1);
	list_show(list1);
#endif

#if 0
	printf("reverse\n");
	list_reverse(list1);
	list_show(list1);
#endif

#if 0
	list_node_t * list2 = list_create();
	printf("Please enter insert value \n");
	printf("enter q to quit > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		list_tail_insert(list2, value);
		printf("Please enter next > ");
		fflush(stdout);
	}
	list_show(list1);
	printf("merge");
	list_merge(list1, &list2);
	list_show(list1);
#endif

	list_free(&list1);

    return 0;
}/*}}}*/

四、栈

[1] 概念
  1. 特点:
    • 栈是限制在一端进行插入(入栈或压栈)和删除(出栈或弹栈)操作的线性表
    • 允许进行操作的一端称为栈顶,另一端称为栈底,当栈中没有元素称为空栈。
    • 先进后出FILO
[2] 顺序栈
head.h
#include <datas.h>

typedef struct liststack {
	int * data;  //存放的数据
	int max;  //栈最多存放元素个数
	int top; //栈现有的元素个数
}stack_t;

//judge stack exit
void stack_exit(stack_t *);

//create stack
stack_t *stack_create(int);

//push stack
int stack_push(stack_t * pt, int value);

//out stack
int stack_out(stack_t *);

//judge stack empty
int stack_empty(stack_t *);

//judge stack full
int stack_full(stack_t *);

//stack free
int stack_free(stack_t **);

//view stack up
int stack_top(stack_t *);
func.c
#include "head.h"

//stack exit
void stack_exit(stack_t * pt)  //判断栈指针是否为NULL
{/*{{{*/
	if (pt == NULL)
		FPRINT_E("error: pt is NULL\n");
}/*}}}*/

//create stack
stack_t *stack_create(int len) //len
{/*{{{*/
	if (len <= 0)
		FPRINT_E("len <= 0 error \n");
	stack_t * stack = (stack_t *) malloc(sizeof(stack_t));
	if (NULL == stack)
		FPRINT_E("malloc error \n");

	stack->data = (int *) malloc(len * sizeof(int));
	if (NULL == stack->data)
    {
        free(stack);  //别忘记释放刚刚申请的stack
        stack == NULL;
		FPRINT_E("stack data malloc error \n");
    }
		

	memset(stack->data, 0, sizeof(int) * len);
	stack->max = len;  //能存放最大元素个数
	stack->top = 0;   //现有元素个数

	return stack;
}/*}}}*/

//push stack
int stack_push(stack_t * pt, int value)  //入栈(尾入)
{/*{{{*/
	stack_exit(pt);

	if(pt->top >= pt->max)  //判断是否栈满
		FPRINT_E("stack full not insert\n");

	pt->top++;   //现有元素加一
	pt->data[pt->top-1] = value;  

	return 0;
}/*}}}*/

//out stack
int stack_out(stack_t * pt)  ///出栈(尾出)
{/*{{{*/
	stack_exit(pt);

	if (pt->top <= 0)    //如果此时栈已空,那就不能继续出栈
		FPRINT_E("error: stack empty \n");

	int ret = pt->data[pt->top-1];
	pt->top--;

	return ret;
}/*}}}*/

//judge stack empty
int stack_empty(stack_t * pt)  //判栈空
{/*{{{*/
	stack_exit(pt);

	return (pt->top == 0 ? 1 : 0);
}/*}}}*/

//judge stack full
int stack_full(stack_t * pt)  //判栈满
{/*{{{*/
	stack_exit(pt);

	return (pt->top == pt->max ? 1 : 0);
}/*}}}*/

//stack free
int stack_free(stack_t ** pt)  //释放栈
{/*{{{*/
	stack_exit(*pt);

	free((*pt)->data);  //因为我们申请了两次,所以释放也要释放两次,先释放data
	(*pt)->data = NULL;   //这个可以省略,但是为了规范,还是加上这一句
	free(*pt);  //然后释放pt
	*pt = NULL;  //pt 置为NULL

	return 0;
}/*}}}*/

//view stack up  
int stack_top(stack_t * pt)  //查看栈顶元素
{/*{{{*/
	stack_exit(pt);

	return pt->data[top-1];
}/*}}}*/
main.c
#include "head.h"

#define LEN 8
int main(int argc, const char *argv[])
{
	int value, ret;
	stack_t * stack = stack_create(LEN); // 创建栈,里面最多LEN个元素

	printf("please enter push stack value (enter q to quit) > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		ret = stack_push(stack, value);  //入栈
		if (stack->top == stack->max)  //如果栈满,跳出循环
			break;
		printf("please enter next > ");
		fflush(stdout);
	}
	//printf("stack empty: %d \n", stack_empty(stack));
	//printf("stack full: %d \n", stack_full(stack));

	while (stack->top > 0)  //出栈,一直出到栈空
	{
		ret = stack_out(stack);
		printf("out stack : %d\n", ret);
	}

	printf("stack empty: %d \n", stack_empty(stack));
	printf("stack full: %d \n", stack_full(stack));

	stack_free(&stack);

    return 0;
}
[3] 链栈
head.h
#include <datas.h>

typedef struct stack_node {   //链栈节点结构
	int data;   //数据域
	struct stack_node * next;  //指针域
}stack_node_t;

//create stack
stack_node_t * stnode_create(void);  //创建链栈

//stack node exit
void stnode_exit(stack_node_t *); //判断栈是否存在

//stack empty
int stnode_empty(stack_node_t *);  //判断链栈是否为空

//push stack
int stnode_push(stack_node_t *, int);  //入栈

//out stack
int stnode_out(stack_node_t *);  //出栈
	
//stack free
int stnode_free(stack_node_t **);  //释放链栈
func.c
#include "head.h"

//create stack
stack_node_t * stnode_create(void)
{/*{{{*/
	stack_node_t * pt;
	pt = (stack_node_t *) malloc(sizeof(stack_node_t));
	if (NULL == pt)
		FPRINT_E("error: pt application malloc \n");

	pt->data = -1;   //初始化头节点
	pt->next = NULL;

	return pt;
}/*}}}*/

//stack node exit
void stnode_exit(stack_node_t * pt)
{/*{{{*/
	if (NULL == pt)
		FPRINT_E("error pt is NULL\n");
}/*}}}*/

//stack empty
int stnode_empty(stack_node_t * pt)  //判链栈是否为空
{/*{{{*/
	stnode_exit(pt);
	return (NULL == pt->next ? 1 : 0);
}/*}}}*/

//push stack  head insert
int stnode_push(stack_node_t * pt, int value)  //入链栈,如果现在头入栈等会就必须头出栈
{/*{{{*/
	stnode_exit(pt);
	stack_node_t *p = stnode_create();  //创建一个新节点
	
	p->data = value;  //存放我们需要放入的值
	p->next = pt->next;  
	pt->next = p;

	return 0;
}/*}}}*/

//out stack  // 头出
int stnode_out(stack_node_t * pt)
{/*{{{*/
	stnode_exit(pt);
	int ret;

	if (pt->next == NULL)  //判断链栈是否为空
		FPRINT_E("error: stack empty, not out\n");
	stack_node_t * p = pt->next;  //定义一个指针,指向第一个节点

	pt->next = p->next;  //然后把第二个节点的首地址赋值给头节点的指针域
	ret = p->data;  //出栈的值
	free(p);  //释放空间
	p = NULL;  

	return ret;
}/*}}}*/

//view stack top cell
int stnode_top(stack_node_t * pt)  //查看链栈的栈顶元素
{
	stnode_exit(pt);

	if (pt->next == NULL)  //如果栈空,那么就没有元素
		FPRINT_E("stack node empty, not data\n");

	return (pt->next->data); //返回第一个元素的数据域
}

//stack free
int stnode_free(stack_node_t **pt)  //释放链栈
{/*{{{*/
	stnode_exit(*pt);
	stack_node_t * q = (*pt)->next;  //定义两个变量,q是p的下一个元素,每次释放q,然后
	stack_node_t * p;
	// 先让p = q,然后q向后移动,然后释放p
	while (q != NULL)  
	{
        p = q;
        q = q->next;
		free(p);
	}
	*pt = NULL;  //全部释放完毕,然后把指向头节点的指针指向NULL

	return 0;
}/*}}}*/
出入链栈的第二种方法
******************************************************
//刚刚上面是从头入栈,那么出必须从头出,其实也可以从尾入栈,从尾出栈,
//但是从尾出入栈都需要找到最后一位或则最后两位节点的地址,时间复杂度度高,不如从头出入栈
#if 0
//tail in stack
int stnode_tail_in(stack_node_t * pt, int value)
{/*{{{*/
	stnode_exit(pt);
	stack_node_t *p, *q;  //申请两个变量
	p = stnode_create();  //创建一个新节点(此时新节点已经初始化过),用p变量指向该节点
	p->data = value;  //给新节点数据域赋值
    p->next = NULL;
	q = pt; 

	while (q->next != NULL) //让q变量循环,找到最后那个元素
		q = q->next;
	q->next = p;  //把链栈最后一个元素的指针与指向新节点的手地址即可

	return 0;
}/*}}}*/

//tail out stack
int stnode_tail_out(stack_node_t * pt)  //从尾出链栈
{/*{{{*/
	stnode_exit(pt);
	int ret;
	stack_node_t *p, *q; //仍然定义两个变量
	p = pt;
	if (pt->next == NULL)  //判断链栈是否为空
		FPRINT_E("error: stack empty, can not out\n");

	while (p->next->next != NULL)  //循环,找到倒数第二个节点首地址
		p = p->next;
	q = p;  //令q也指向该地址
	p = p->next; //p继续往下指,此时的p就是最后一个元素,即为出栈的元素
	q->next = NULL;   //先让q的指针域指向NULL,表明链表到q结束
	ret = p->data;  //获取出栈元素的数据
	free(p);   //释放出栈元素的节点
	p = NULL;  //指向NULL

	return ret;  //返回出栈节点的数据
}/*}}}*/
#endif
main.c
#include "head.h"

int main(int argc, const char *argv[])
{/*{{{*/
	stack_node_t * pt1 = stnode_create();
	int value;

	printf("Please enter push stack value.\n");
	printf("enter q to quit > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		stnode_push(pt1, value);
        //printf("view stack node cell: %d\n", stnode_top(pt1));  //查看栈顶元素
		printf("Please enter next > ");
		fflush(stdout);
	}
	printf("stack_empty: %d\n", stnode_empty(pt1));	
    printf("view stack node cell: %d\n", stnode_top(pt1));

	while (!stnode_empty(pt1))
		printf("out: %d\n", stnode_out(pt1));
	printf("stack_empty: %d\n", stnode_empty(pt1));	

	stnode_free(&pt1);

    return 0;
}/*}}}*/

五、队列

[1] 特点:
  1. 先进先出
[2] 顺序队列(循环队列)
head.h
#include <datas.h>

#define N 9
typedef struct {
	int * data;     //数据
	int front, rear; //两个数字,一个代表首元素的下标一个代表队列元素个数,
    				//插入的时候rear加一,出队的时候front加一
}queue_t;        //两个指针相等的时候为队空

//create queue
queue_t *queue_create(int);

//queue exit
void queue_exit(queue_t *);

//judge queue empty
int queue_empty(queue_t *);  //判空

//judge queue full
int queue_full(queue_t *); //判满

//in queue
int queue_in(queue_t *, int);  //入队

//out queue
int queue_out(queue_t *); ///出队

//queue free
int queue_free(queue_t **);  //释放队列
func.c
#include "head.h"

//create queue
queue_t *queue_create(int len)  //创建队列
{/*{{{*/
	queue_t * queue = (queue_t *) malloc(sizeof(queue_t));
	if (NULL == queue) 
		FPRINT_E("error: application malloc \n");

	queue->data = (int *) calloc(len, sizeof(int));  //分配内存并清零
	queue->front = queue->rear = 0;  //开始队列为空,两个数都初始化为0

	return queue;
}/*}}}*/

//queue exit
void queue_exit(queue_t * pt)
{/*{{{*/
	if (NULL == pt)
		FPRINT_E("error: queue is NULL\n");
}/*}}}*/

//judge queue empty
int queue_empty(queue_t * pt)
{/*{{{*/
	queue_exit(pt);
	return (pt->rear == pt->front ? 1 : 0);  //相等的时候说明队空了
}/*}}}*/

//judge queue full
int queue_full(queue_t * pt)
{/*{{{*/
	queue_exit(pt);
	return ((pt->rear+1) % N == pt->front ? 1 : 0);  //运用对长度取余来循环利用,
}/*}}}*/   //此时会浪费一个位置,但是可以重复利用,当(rear+1)%N和front相等的时候为队满

//in queue
int queue_in(queue_t *pt, int value) //尾入
{/*{{{*/
	queue_exit(pt);
	if ((pt->rear+1)%N == pt->front)
		FPRINT_E("error: queue full, not in\n");

	pt->data[pt->rear] = value;  //这里一定要先赋值,然后再让rear加一取余
	pt->rear = (pt->rear + 1) % N; //如果先取余在赋值的话,赋值下标就要减一,会出现data[0 - 1]
								//data[-1]实际上应该是data[N-1]
	return 0;
}/*}}}*/

//out queue
int queue_out(queue_t * pt)  //出队  ,头出
{/*{{{*/
	queue_exit(pt);
	if (pt->rear == pt->front)  //如果队空,那么不能继续出队
		FPRINT_E("error: queue empty , not out\n");

	int ret = pt->data[pt->front];  //front就为最开始元素的下标,出该元素即可
	pt->front = (pt->front + 1) % N;  //然后front加一

	return ret;
}/*}}}*/

//queue free
int queue_free(queue_t ** pt)  //释放队列
{/*{{{*/
	queue_exit(*pt);

	free(*pt);
	*pt = NULL;

	return 0;
}/*}}}*/
main.c
#include "head.h"

int main(int argc, const char *argv[])
{/*{{{*/
	queue_t * queue = queue_create(N);  //注意队列中存放最大元素应该是N-1,不是N

	int value;
	printf("Please enter value (enter q to quie) > ");
	fflush(stdout);
	while (scanf("%d", &value))
	{
		queue_in(queue, value);
		if ((queue->rear+1) % N == queue->front)  //如果队列满了就退出循环
			break;
		printf("Please enter next > ");
		fflush(stdout);
	}
	printf("queue empty: %d\n", queue_empty(queue)); //判空
	printf("queue full: %d\n", queue_full(queue));  //判满

	while (!queue_empty(queue))  //如果不为空就出队
		printf("out: %d\n", queue_out(queue));  //出队
	printf("queue empty: %d\n", queue_empty(queue));
	printf("queue full: %d\n", queue_full(queue));

	queue_free(&queue);

    return 0;
}/*}}}*/
[3] 链队列
head.h
#include <datas.h>

typedef struct queuenode{  //对列节点
	int data;
	struct queuenode * next;  //指针域
}queue_node_t;

typedef struct {
	queue_node_t * front;  //永远指向头节点
	queue_node_t * rear;  //永远指向最后一个节点的首地址
}point_t;  //我们用front 和 rear 两个指针来操作链队,还是一样两个指针指向对方相同时列空

//create queue node
point_t *point_create(void);  //创建一个节点,并初始化,值为-1,指针域指向NULL

//queue exit
void queue_node_exit(queue_node_t *);  //链节点是否存在

//point exit
void point_exit(point_t *);  //指向链队的指针是否指向NULL

//in queue node 
int queue_node_in(point_t *, int);  //入链队

//out queue node
int queue_node_out(point_t *);  //出链队

//judge queue node empty
int queue_node_empty(point_t *);  //判断链队是否为空

//queue node free
int queue_node_free(point_t **);    //释放链队
func.c
#include "head.h"

//create queue node
point_t *point_create(void)  //创建链队
{/*{{{*/
	point_t * point = (point_t *) malloc(sizeof(point_t));  //为指针申请内存
	if (NULL == point)
		FPRINT_E("error: point malloc\n");
	//为节点申请内存
	point->front = point->rear = (queue_node_t *) malloc(sizeof(queue_node_t));
	if (NULL == point->front)
	{  //如果申请失败就释放刚刚申请的指针内存
		free(point);
		point = NULL;
		FPRINT_E("error: queue malloc\n");
	}

	point->front->data = -1;   //初始化都指向头节点,里面的值初始化为-1
	point->front->next = NULL;  //指针域指向NULL

	return point;
}/*}}}*/

//queue_exit
void queue_node_exit(queue_node_t * ptq)  //判断节点是否存在
{/*{{{*/
	if (NULL == ptq)	
		FPRINT_E("error: ptq is NULL\n");
}/*}}}*/

//point exit
void point_exit(point_t * ptp) 
{/*{{{*/
	if (NULL == ptp)
		FPRINT_E("error: ptp is NULL\n");
}/*}}}*/

//in sqeue node  tail insert
int queue_node_in(point_t * pt, int value)  //入链队(尾插)
{/*{{{*/
	point_exit(pt);
	queue_node_t * q = (queue_node_t *) malloc(sizeof(queue_node_t));  //创建新节点
	if (q == NULL)
		FPRINT_E("error: malloc\n");

	q->data = value;
	q->next = NULL;  //尾插,所以该元素为最后一个元素,指针域初始化为0
	ptp->rear->next = q;  //rear指向最后一个节点的首地址,让其指针域指向新节点q
	ptp->rear = q;  //此时新节点成为最后一个节点,然后让rear继续指向最后一个节点

	return 0;
}/*}}}*/

//out queue node   head out
int queue_node_out(point_t * pt)  //出列(头出)
{/*{{{*/
	int ret;
	point_exit(pt);
	if (pt->front == pt->rear)   //如果链队列为空,那么不能再出列了
		FPRINT_E("error: queue node empty, can not out\n");
	//按道理来说,应该free第0号节点,而不是头节点,但是如果这样那么队列为空的条件就要改变,所以
    //我们释放头节点,把第0号节点当作头节点
	queue_node_t * p; //定义一个中间指针
	p = pt->front;  //指针指向头节点
	pt->front = pt->front->next; //front指向第一个元素
	ret = ptp->front->data;  //记录第0号元素的值
	free(p);  //释放头节点
	p = NULL;  

	return ret;
}/*}}}*/

//queue node free
int queue_node_free(point_t ** ptp)  //释放链队
{/*{{{*/
	point_exit(*ptp);
	queue_node_t * p = (*ptp)->front;
	//每次循环开始让p指向front指向的地址,每次释放p,然后把front往后移一位
	while ((*ptp)->front != NULL)
	{
		p = (*ptp)->front;	
		(*ptp)->front = (*ptp)->front->next;
		free(p);  //释放p
	}
	free(*ptp);  //最后别忘了释放指针结构体
	*ptp = NULL;
	
	return 0;
}/*}}}*/
入列出列第二种方法
//一样这里也可以使用从头入列,从尾出列的方法,但是出列的时候要遍历到倒数第二个元素,时间复杂度高
#if 0
//in queue node   head insert
int queue_node_in(point_t * pt, int value)
{/*{{{*/
	point_exit(pt); //先判断pt是否为NULL
	queue_node_t * q = (queue_node_t *) malloc(sizeof(queue_node_t));  //创建新节点

	q->data = value;  //赋值
	q->next = pt->front->next;  //让新节点的指针域指向第0号元素
	if (q->next == NULL)   //此时如果链队列为空插入的话,新节点就是最后一个元素(因为使用的头插)
		pt->rear = q;   //此时要把rear指向最后一个元素,即刚开始插入的元素首地址
	pt->front->next = q;

	return 0;
}/*}}}*/

//out queue node  tail out
int queue_node_out(point_t * pt)
{/*{{{*/
	int ret;
	point_exit(pt);
	if (pt->front == pt->rear)  //如果链队列为空,那么便不能继续出列了
		FPRINT_E("error: queue node empty.\n");
	queue_node_t * p, *q;  //定义两个中间变量
	p = pt->front;

	while (p->next->next != NULL)  //p一直循环,直到p为倒数第二个元素
		p = p->next;
	q = p->next;  //让q指向倒数第一个元素,q即为出列的元素
	p->next = NULL;  //出列后,p指向的指针为最后一个节点,指针域置为NULL
	pt->rear = p;  //然后继续把rear指向最后一个元素
	ret = q->data;  //获取出列节点的数据
	free(q);  //释放节点
	q = NULL;

	return ret;
}/*}}}*/
#endif
main.c
#include "head.h"

int main(int argc, const char *argv[])
{/*{{{*/
	point_t * pt = point_create();
	point_exit(pt);
	int value;
	
	printf("Please enter value (enter q to quit) > )");	
	fflush(stdout);
	while (scanf("%d", &value))
	{
		queue_node_in(pt, value);
		printf("Please enter next > ");
		fflush(stdout);
	}
	printf("empty: %d\n", queue_node_empty(pt));
	while (!queue_node_empty(pt))  //如果不为空队列就继续循环
		printf("out: %d\n", queue_node_out(pt));  //出列
	printf("empty: %d\n", queue_node_empty(pt));

	queue_node_free(&pt);

    return 0;
}/*}}}*/

六、树形结构

  • 就像一棵倒立的树,有唯一的树根,树根可以发出多个分支,每个分支也可以继续发出分支,树枝和树枝之间是不相交的。
  • 树(tree)是nn≥0)个节点的有限集合,当n=0时,为空树;n>0时,为非空树。任意一棵非空树,满足以下两个条件:
    • 有且仅有一个称为根的节点;
    • 除根节点以外的其余节点可分为mm>0)个互不相交的有限集T1, T2, …, T**m,其中每一个集合本身又是一棵树,并且称为根的子树(subtree)。

节点——节点包含数据元素及若干指向子树的分支信息。

节点的度——节点拥有的子树个数。

树的度——树中节点的最大度数。

终端节点——度为0的节点,又称为叶子。

分支节点——度大于0的节点。除了叶子都是分支节点。

内部节点——除了树根和叶子都是内部节点。

节点的层次——从根到该节点的层数(根节点为第1层)。

树的深度(或高度)——指所有节点中最大的层数。

路径——树中两个节点之间所经过的节点序列。

路径长度——两节点之间路径上经过的边数。树中没有环,因此树中任意两个节点之间的路径都是唯一的。

双亲、孩子——节点的子树的根称为该节点的孩子,反之,该节点为其孩子的双亲。

兄弟——双亲相同的节点互称兄弟。

堂兄弟——双亲是兄弟的节点互称堂兄弟。

祖先——从该节点到树根经过的所有节点称为该节点的祖先。

子孙——节点的子树中的所有节点都称为该节点的子孙。

有序树——节点的各子树从左至右有序,不能互换位置。

无序树——节点各子树可互换位置。

森林——由mm≥0)棵不相交的树组成的集合。

[1] 二叉树
  1. 非空树T满足以下两个条件:

    • 有且仅有一个称为根的节点;

    • 除根节点以外,其余节点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身都是二叉树。

  2. 在二叉树的第i层上至多有2(i−1)个节点,深度为k的二叉树至多有(2k)−1个节点

  3. 满二叉树:一棵深度为k且有2k−1个节点的二叉树。满二叉树每一层都“充满”了节点,达到最大节点数。

  4. 完全二叉树:除了最后一层外,每一层都是满的(达到最大节点数),最后一层节点是从左向右出现的。

[2] 树的遍历
  1. 先序遍历(根左右):先访问根节点,再访问左节点,最后访问右节点

  2. 中序遍历(左根右):先访问左节点,再访问根节点,最后访问右节点

  3. 后续遍历(左右根):先访问左节点,再访问右节点,最后访问根节点

    数据结构

  4. 如果上图按先序遍历的话

    • '#' 表示虚拟节点,弄这些虚拟节点的目的就是为了递归

    数据结构

[3] 构建二叉树
  1. 步骤:
    • 按照先序遍历构建二叉树,先建立根节点 根据输入的“标识数据”来判断是否创建节点(如果不是#则创建根节点)
    • 再创建左子树
    • 最后创建右子树
    • 如果用户输入输入的是 # 代表递归的出口
    • 如果输入的不是 # 则创建子树节点
head.h
#include <datas.h>

typedef struct tree_node {
	char data;//这里用的是字符做实验,所以这里是char类型,也可以换成int类型,但是结束条件就要重新考虑
	struct tree_node * left;  //指向左节点
	struct tree_node * right;  //指向右节点
}btree_node_t;

//create btree
btree_node_t *tree_create(void);  //依据给定的字母来创建二叉树

//front traverse
void front_traverse(btree_node_t *);  //先序遍历

//center traverse
void center_traverse(btree_node_t *); //中序遍历

//rear traverse
void rear_traverse(btree_node_t *);  //后续遍历

func.c

#include "head.h"

//create btree
 btree_node_t *tree_create(void)  //创建树(递归)
{/*{{{*/
	char ch;
	btree_node_t * pt;
	//printf("Please enter character > ");
	//fflush(stdout);
	scanf("%c", &ch);
	if ('#' == ch)   //如果输入的是 # ,表明是虚拟节点,递归结束,此时返回空指针(退出条件)
		return NULL;
	pt = (btree_node_t *) malloc(sizeof(btree_node_t));
	if (NULL == pt)
		FPRINT_R("error: pt is NULL\n", NULL);

	pt->data = ch;  //存放值 
	pt->left = tree_create();  //左节点递归,如果再次获取的值不为#,就继续递归,如果为#就返回NULL
	pt->right = tree_create();  //同上

}/*}}}*/

//front traverse
void front_traverse(btree_node_t * pt)  //先序遍历
{/*{{{*/
	if (NULL == pt)  //递归退出的条件
		return ;
	//先序遍历根左右,pt即为根节点,所以先打印根节点,然后再开始递归左节点,
    //左节点就去后又变成了下一个的根节点,以此类推,右节点也是,一直递归
	printf("%c ", pt->data);  
	front_traverse(pt->left);
	front_traverse(pt->right);
}/*}}}*/

//center traverse
void center_traverse(btree_node_t * pt)  //中序遍历
{/*{{{*/
	if (NULL == pt)  //递归退出的条件
		return ;
	//中序递归(左根右),所以先递归左节点,然后打印,最后递归右节点
	center_traverse(pt->left);
	printf("%c ", pt->data);
	center_traverse(pt->right);
}/*}}}*/

//rear traverse
void rear_traverse(btree_node_t * pt)  //后序遍历
{/*{{{*/
	if (NULL == pt)  //递归推出的条件
		return ;
	//后序递归,所以先递归左节点,然后递归右节点,最后打印根节点
	rear_traverse(pt->left);
	rear_traverse(pt->right);
	printf("%c ", pt->data);
}/*}}}*/

//free tree
void tree_free(btree_node_t ** pt)  //释放树
{/*{{{*/
	if (NULL == *pt)
		return ;
	//递归释放,先一直遍历到最后,然后再释放
	tree_free(&(*pt)->left); 
	tree_free(&(*pt)->right);
	free(*pt);
	pt = NULL;
}/*}}}*/
main.c
#include "head.h"

int main(int argc, const char *argv[])
{
	printf("Please enter character > "); //按先序遍历来输入
	fflush(stdout);
	btree_node_t * pbtree = tree_create();

	front_traverse(pbtree);
	puts("\n-----------------");
	center_traverse(pbtree);
	puts("\n-----------------");
	rear_traverse(pbtree);
	puts("\n-----------------");
	tree_free(&pbtree);

    return 0;
}

七、哈希查找

查找算法:顺序查找、折半查找、哈希查找。所有好的查找算法都是为了减少查找时间复杂度,但是目前哈希查找的效率最高

原理:创建一个数组,里卖装的元素都是结构体,这个结构体就节点,来一个元素,让它对表长取余,得到数字即为它要插入数组的下标,如果有重复插入就在链表后面插入,

数据结构

[1] 哈希查找的步骤
  1. 创建哈希表 (哈希表长 = 数据元素的个数 /( 0.7--0.8))
    • 例如 k={ 23,34,14,38,46,16,68,15,07,31,26 }11 / 0.75 = 15; ,所以我们设哈希表长为15
[2] 哈希查找代码实现
head.h
#ifndef __HASH_H__
#define __HASH_H__

#define N 15

#include <datas.h>

typedef struct hash_node {
	int key;   //存储哈希表的下标
	int data;    //存储节点数据
	struct hash_node * next;  //指向下一个节点的首地址,如果没有就指向NULL
}hash_node_t;

typedef struct arr {
	hash_node_t hash_list[N];
}hash;

//create hash
hash *hash_create(void);   //创建哈希表

//insert hash
int hash_insert(hash *, int);  //插入数据

//find hash
hash_node_t *hash_find(hash *, int value);  //按值查找数据

//free hash
void hash_free(hash **);  //释放哈希表

#endif
func.c
#include "head.h"

//create hash
hash *hash_create(void)  //创建哈希表
{
	hash * pt = (hash *) calloc(1, sizeof(hash));  //申请内存并初始化为0
	if (NULL == pt)
		FPRINT_E("error: malloc hash.\n");

	return pt;
}

//hash exit
void hash_exit(hash * pt)  //判断哈希表是否存在
{
	if (NULL == pt)
		FPRINT_E("error: pt is NULL\n");
}

//insert hash
int hash_insert(hash *pt, int value)  //插入到哈希表中
{
	hash_exit(pt);  //判断哈希表是否存在

	hash_node_t *p = (hash_node_t *) malloc(sizeof(hash_node_t));//申请节点内存
	if (NULL == p)
		FPRINT_E("error: calloc p \n");

	p->key = value % N;  //初始化为数组下标
	p->data = value;  //初始化为存放的数据
	p->next = NULL;  //初始化为NULL,因为等会插入是尾插,所以该元素节点一定是最后一个节点元素
	//pt->hash_list[p->key] 的结构为一个结构体,所以要对它取地址才能变成指向该结构体的指针
	hash_node_t * q = &(pt->hash_list[p->key]);  //再定义一个指针变量
	while (q->next)  //一直循环,直到q->next为NULL的时候才跳出循环
		q = q->next;
	q->next = p; //此时让q->next指向p即可

	return 0;
}

//find hash
hash_node_t *hash_find(hash * pt, int value)
{
	hash_exit(pt);
	hash_node_t * q = &(pt->hash_list[value % N]);  //定义指针变量并初始化
    //此时指向的就是头节点

	while (q)  //从头节点开始循环,一直找到结尾,看是否有值等于我们查找的值
	{
		if (q->data == value) //如果有就跳出循环,如果没有就继续循环,直到最后退出
			break;
		q = q->next;
	}
	if (q == NULL)  //如果最后退出是因为q为NULL退出,那么说明没有找到,就返回NULL
		return NULL;
	else
		return q;  //否则就返回节点首地址
}

//free hash
void hash_free(hash ** pt)  //释放哈希表
{/*{{{*/
	hash_exist(*pt);

	hash_node_t * p, * q; //定义两个指针变量
	for (int i = 0; i < N; i++)  //先循环遍历整个数组
	{
		p = &((*pt)->hash_list[i]);  
		if (NULL == p->next)  //如果数组元素的指针域为NULL,说明后面没有链表节点
			continue;
		else   //如果数组元素的指针域不为NULL,证明后面有链表,接需要释放链表节点
		{
			q = p->next;  //每次让p = q ,然后q往后移动一位,释放p节点
			while (q != NULL)
			{
				p = q;
				q = p->next;
				free(p);
			}
		}
	}

	free(*pt);  //最后别忘了释放哈希表
	*pt = NULL;
}/*}}}*/
main.c
#include "head.h"

int main(int argc, const char *argv[])
{
	hash * pt = hash_create();
	int data[] = {23, 34, 14, 38, 46, 16, 68, 15, 7, 31, 26};
	int i;

	int key;
	for (i = 0; i < sizeof(data) / sizeof(int); i++)  //循环插入(尾插)
		hash_insert(pt, data[i]);

	printf("Please enter cell you want to find > ");
	fflush(stdout);
	scanf("%d", &key);
	hash_node_t * ret = hash_find(pt, key);
	puts("----------------");
	if (NULL == ret)  //如果返回值为NULL,那么就没有找到,反之就找到了
		printf("not find\n");
	else
		printf("find: %d\n", ret->data);

	hash_free(&pt);
		
    return 0;
}

八、快速排序

[1] 原理
  1. 每次选一个基准值(一般选第0号元素的值),(开始 i 指向开头,j 指向结尾)然后让它和 j 所在的元素比较,如果大于(或者小于),就交换,反之就 j 自减,如果交换了,那么 i++ 继续比较 i 所在的元素和 j 所在的元素(此时 j 所在的元素就是基准元素),直到最后 i = j ,此时就是基准元素所在的位置,此时比基准元素大的都是基准元素左边或者右边,比基准元素小的都在基准元素的右边或者左边,基准元素把一个表分成两个表,然后把两个表重复刚刚操作,所以这里可以用到递归
[2] 实现

main.c

#include <datas.h>
#include <time.h>
#define N 10

int quick_sort(int *, int, int);  //排序函数
int partion(int *, int, int);  //返回基准值的下标

int main(int argc, const char *argv[])
{/*{{{*/
	int data[N] = {0};
	int i;
	srand((unsigned int) time(0));  //为了创建一个随机数列所以要初始化种子
	for (i = 0; i < N; i++)
		data[i] = rand() % 100;  //fill arr
	for (i = 0; i < N; i++)
		printf("data[%d] = %d\n", i, data[i]);
	puts("---------------\n");

	quick_sort(data, 0, N - 1);   //排序函数
	for (i = 0; i < N; i++)
		printf("data[%d] = %d\n", i, data[i]);  //打印结果

    return 0;
}/*}}}*/

int quick_sort(int * data, int low, int high)
{/*{{{*/
	if (data == NULL)	
		FPRINT_E("error: data is NULL \n");

	if (low >= high)
		return 0;

	int t;
	t = partion(data, low, high);  //返回基准值的下标
    //此时基准值左边的值就是比基准值小,然后让把基准值左边的值当成一个新的数组,
    //这个新数组下标为0到t-1,右边的数组下标为t+1到N-1,把这两个数组继续作为partion的参数
    //继续找基准值的位置,找到只剩一个元素的时候(即输入下标low 和 high 相等的时候)退出
 	quick_sort(data, low, t-1);  
	quick_sort(data, t+1, high);
}/*}}}*/

int partion(int *data, int low, int high)//(low, high下文用i, j代替)
{/*{{{*/
	int temp = data[low];  //获取基准值
	int flags = 0;
	//这里是从小到大排序
	while (low < high)  //当low小于high的时候说明里面元素不止一个,还可以继续分
	{
		if (flags == 0)  //标志位,flags = 0 的时候,让基准值和 j 所在的下标进行比较
		{  //此时如果基准值大于 J 下标所在的值,那就交换,反之就让j--
			if (temp <= data[high])  //如果基准小于j此时的值, 那么j--
				high--;
			else   //如果temp > data[high] 说明需要交换(大的放右边,小的放左边)
			{   //交换后,就该让 i++ 了,而且以后都是和 i 所在的下标进行比较,所以把 flags 置为1
				data[low] = data[high];//这里不用严格交换,只需要赋值就行,因为temp始终为基准值 
				low++;
				flags = 1;
			}
		}
		if (flags == 1) //标志位,flags = 1 的时候,让基准值和 i 所在的下标进行比较
		{  //此时如果基准值小于 i 下标所在的值,那就交换,反之就让i++ (始终把较大的值放到后面)
			if (temp >= data[low])  
				low++;
			else  //此时需要交换,交换后就该让 j-- 了,而且以后都是和 j 所在下标的值就行比较
			{
				data[high] = data[low];
				high--;
				flags = 0;
			}
		}
	}
	data[low] = temp;  //最后退出循环后low 和 high 相等,该处就是基准值所在的位置

	return high;  //返回基准值所在下标
}/*}}}*/
[3] qsort接口的调用
  1. qsort

    • 函数原型:void qsort(void *array, size_t number, size_t size, int (*compare) (const void *, const void *));

    • 头文件:stdlib.h

    • 参数:

      • arr:指针,指向带排序数组,因为是void * 所以可以传递任何类型的数组

      • number:待排序项的数量,函数原型中吧该值转换成size_t类型

      • size:待排序数组中每个元素的大小,sizeof(int) sizeof(double)

      • compare:函数指针,这个函数用于确定排序的顺序,它他应该接受两个参数,分别指向待比较两项的指针,如果第一项大于第二项,比较函数返回正数,如果相同返回0,如果小于第二项,返回负数(此时是从小到大排列),如果第一项大于第二项返回负数,反之返回正数(此时是从大到小排序)

      • 返回值:无

      • 实例

        #include <stdio.h>
        #include <stdlib.h>
        
        #define LEN 40
        
        void fillarray(double [], int);  //创建数组
        void showarray(double *, int); //展示数组
        int mycompare(const void *, const void *);  //比较函数
        
        int main(int argc, const char *argv[])
        {
        	double arr[LEN];
        
        	fillarray(arr, LEN);  //填充数组
        	puts("Random list");
        	showarray(arr, LEN);  //遍历展示数组
        	qsort(arr, LEN, sizeof(double), mycompare);  //排序
        	puts("\nSorted list:");
        	showarray(arr, LEN);  //展示排序完后的数组
        
            return 0;
        }
        
        void fillarray(double ar[], int n)
        {/*{{{*/
        	int i;
        
        	for (i = 0; i < n; i++)
        		ar[i] = (double) rand() / ((double) rand() + 0.1);
        }/*}}}*/
        
        void showarray(double * ptd, int n)
        {/*{{{*/
        	int index;
        
        	for (index = 0; index < n; index++)
        	{
        		if (index % 6 == 0 && index != 0)
        			putchar('\n');
        		printf("%9.4f ", ptd[index]);
        	}
        	if (index - 1 % 6 != 0)
        		putchar('\n');
        }/*}}}*/
        
        int mycompare(const void * p1, const void * p2)
        {/*{{{*/
        	const double * ptd1 = (const double *) p1; //注意这里的指针类型转换
        	const double * ptd2 = (const double *) p2;
        
        	if (*ptd1 < *ptd2)
        		return -1;
        	else if (*ptd1 == *ptd2)
        		return 0;
        	else
        		return 1;
        }/*}}}*/
        

九、考试 补充

  1. 如果现在有一个单链表,已知存在一个指针p指向某一个节点,如何删除这个节点

    /*首先要明白,删除节点实质只是改变值而已,以前我们都是找到需要删除节点的前一个元素,现在头节点不知道
    无法找到前一个元素。这里我们只需要一个指针变量 q */
    q = p->next;  //q 指向下一个元素
    p->data = q->data;  //把后面那个元素的值给p节点,此时相当于p就是下一个节点,只需要释放q即可
    p->next = q->next;  //把 p 的指针域指向 q 的指针域
    free(q);  //释放q
    q = NULL;
    
    
  2. 对于一个循环队列,如何计算队列中的元素

    
    
上一篇:R语言 t分布(不同*度)


下一篇:css 伪类 属性选择器