IO 数据结构

一、IO

(一)文件分类

文本文件:存储ASCII码(0~127),以EOF作为结束符,一般存储数据量比较大的信息。

二进制文件:是数据在内存上的原样存储,存储的是二进制形式,一般存储中间变量,数据量比较小的信息,读取的速度快。

(二)按操作方式来分

带缓冲区的操作:(标准 IO )高级的文件操作,系统会自动的在内存上给我们分配缓冲区。
不带缓冲区的操作:(文件 IO )低级的文件操作,系统是不会自动的给我们的程序分配空间,但是如果需要缓冲区的机制,缓冲区就得自己定义了。

(三)系统调用和库函数

系统调用:操作系统提供给用户直接操作硬件的一组接口
库函数:对操作系统的二次封装
区别:
1、系统调用一般提供基础的功能,库函数提供较为复杂的功能
2、系统调用一般不能重写,库函数可以重写
3、系统调用运行的时间属于机器时间,库函数运行的时间属于用户时间
4、系统调用运行的空间属于内核空间,库函数运行的空间属于用户空间
5、系统调用的返回值一般是非负整数,库函数的返回值不一定
6、系统调用的运行效率没有库函数高
7、系统调用的移植性没有库函数好

(四)文件流指针和文件描述符

FILE文件流指针是一个typedef之后的值,本质是一个结构体在结构体内部保存了文件描述符,文件流指针维护了读写缓冲区。

文件描述符:是一个正整数,其含义为fd_array数组下标

(五)IO分类

标准IO,文件IO

区别:

标准 IO
1. 标准 IO 是由库函数系统提供的,由 ANSI C 标准定义
2. 是带缓冲区的操作,运行效率较高
3. 支持跨平台的
4. 标准 IO 操作的依据一般是流指针
文件 IO
1. 文件 IO 是由操作系统提供的,由 POSIX (可移植操作系统接口)定义
2. 没有缓冲区,运行效率没有标准 IO
3. 不支持跨平台的
4. 文件 IO 操作的依据一般是文件描述符(非负整数)

1、标准IO

(1)缓冲区的方式

全缓冲:缓冲区满了才刷新缓冲区,或者强制刷新缓冲区

行缓冲:碰到换行符刷新缓冲区、缓冲区满了刷新、或者强制刷新缓冲区

不缓冲:所有的信息到缓冲区之后直接到文件

(2)对文件进行操作

打开文件:fopen

 

函数功能:打开由参数 1 描述的文件,打开的方式由参数 2 确定
函数参数 1 :需要被打开的文件的路径
函数参数 2 :打开的方式
r : 以只读的形式打开文件,文件存在则打开,不存在则报错
r+ : 以读、写的形式打开文件,文件存在则打开,不存在则报错
w : 以只写的形式打开文件,文件存在则清空打开,文件不存在则新建
w+ : 以读、写的形式打开文件,文件存在则清空打开,文件不存在则新建
a : 以追加的形式打开文件,文件存在则追加,文件不存在则新建
a+ : 以可读可写(追加)的形式打开文件,文件存在则追加,文件不存在则新建
函数返回值:成功返回:文件流指针
失败返回 NULL ,并且更新 errno

操作文件:

以字符读写:fgetc 和 fputc

以行读写:fgets 和 fputs

以对对象读写:fread 和 fwrite

以格式化读写:fprintf 和 fscanf

关闭文件:fclose

标准IO相关的函数:

强制刷新:fflsh

判断文件是否到末尾:feof

 时间函数:time      localtime

光标移动:fseek

获取光标位置:ftell

获取文件流指针里面的文件描述符:fileno

2、文件IO

打开文件:open

操作文件

读:read

写:write

关闭文件:close

对目录文件的操作:

打开目录:opendir

操作目录:readdir

获取指定文件的全部信息:stact

关闭目录:closedir

二、动态库、静态库

什么是库?
是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于 windows linux 的本质不同,因此二者库的二进制是不兼容的。
动态库和静态库:
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
静态库:
静态库对函数库的链接是放在编译时期( compile time )完成的。
程序在运行时与函数库再无瓜葛,移植方便
浪费空间和资源,因为所有相关的对象文件( object file )与牵涉到的函数库( library )被链接合成一个可执
行文件( executable file )。
--- 》编译时把静态库中的相关代码复制到可执行程序中
优点:程序运行时,无需加载库,运行速度更快
缺点:占用更多磁盘和内存空间,静态库升级后,需要重新编译链接
动态库:
动态库把对一些库函数的链接载入推迟到程序运行的时期( runtime )。
可以实现进程之间的资源共享。
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制。
---- 》编译时仅记录用到哪个共享库中的哪个符号,不复制共享库中的相关代码
优点:程序不包含库中代码,体积比较小,库升级方便,无需重新编译
缺点:在运行需要加载共享库

三、数据结构

(一)

逻辑结构:事物与事物之间在现实生活中的抽象的一种逻辑关系
集合:数据与数据之间除了同属一个集合之外,没有别的关系
线性:一对一
树状关系:一对多
网(图)状关系:多对多
存储结构
顺序存储:在逻辑上相邻的元素,在物理空间上也相邻
优点:查找方便,
存储空间的利用率可以达到 1
缺点:插入和删除不方便,
申请空间的时候,必须是一片连续的空间,对空间的要求比较高
会用空间碎片的产生
链式存储:在逻辑上相邻的元素,在物理空间上不一定相邻
优点:插入元素不需要移动大量的空间
对空间的要求没那么大,
缺点:查找不方便
存储空间的利用率不足 1 ,没有顺序存储的大
索引存储:依据索引表查找数据大概位置,详细查找数据本身(冲突没有解决号的哈希存储)
优点:查找方便
缺点:有索引表的存在,要浪费空间
插入元素,删除元素之后,索引表要更新
哈希存储:根据关键字直接就能定位到记录本身,就能拿到数据
优点:查找方便,插入,删除也方便
缺点:如果哈希函数设计的不合理,查找的效率就会很低
线性结构的链式存储:
链表的分类:
按方向分:单链表、双链表
按是否循环分:循环链表,不循环链表
按是否带头节点:带头结点的链表,不带头结点的链表

(一)顺序表(线性结构的顺序存储)()

1、顺序表的定义

typedef int data_type;   //把int命名成data_type

//定义顺序表
typedef struct list
{
	/* member */
	data_type *data;	//用来指示表的存储空间
	int size;		//用来表示表的空间大小
	int count;		//用来指示表中元素的个数
}List;

2、顺序表的建立

//函数功能:创建顺序表
//函数参数:申请空间的个数
//函数返回值:成功返回创建的空间的首地址,失败返回NULL
List *CreateList(int my_size)
{
 	List *pList = NULL;//初始化
	//申请表的空间
	if(!(pList = (List *)malloc(sizeof(List))))
	{
		perror("error");
		return NULL;
	}
	//给空间置空
	memset(pList, 0, sizeof(List));
	//申请存储空间
	pList->data = (data_type *)malloc(my_size * sizeof(data_type));
	if(!pList->data)
	{
		perror("List memary error");
		return NULL;
	}
	memset(pList->data, 0, my_size * sizeof(data_type));
	pList->size = my_size;
	return pList;
}

3、顺序表的插入


//函数功能:插入元素
//函数参数1:待插入元素的顺序表
//函数参数2:要插入的位置
//函数参数3:要插入的值
//函数返回值:成功返回OK,失败返回失败原因
int InsertItem(List *pList, int pos, data_type item)
{
	//入参判断
	if(!pList) return LIST_NULL;
	//判断表能否被插入
	if(pList->count == pList->size) return LIST_FULL;
	//检查pos有五问题
	if(pos < -1 || pos >= pList->size) return POS_ERR;
	if(pos == -1)
	{
		//尾插
		//1。插入
		pList->data[pList->count] = item;
		pList->count++;
		return OK;
	}
	//插入
	//1.移动元素,为待插入元素预留位置
	for(int i=pList->count; i>pos; i--)
	{
		pList->data[i] = pList->data[i-1];
	}
	//2。插入元素
	pList->data[pos] = item;
	//3。插入成功,成员数目发生变化 +1
	pList->count++;
	return OK;
}

4、顺序表的删除


//函数功能:删除元素
//函数参数1:给谁删除
//函数参数2:删除的位置
//函数参数3:删除的元素保存
//函数返回值:成功返回OK。失败返回失败原因
int DeleteItem(List *pList, int pos, data_type *item)
{
	//入参判断
	if(!pList) return LIST_NULL;
	//判断链表是否为空
	if(pList->count == 0) return LIST_EMPTY;
	//判断POS是否有误
	if(pos < 0 || pos >= pList->count)  return POS_ERR;
	//删除
	//1。保存元素
	*item = pList->data[pos];
	//2。移动位置删除
	for(int i=pos; i<pList->count-1; i++)
	{
		pList->data[i] = pList->data[i+1];
	}
	//3。count--
	pList->count--;
	return OK;
}

5、顺序表的显示

印顺序表
	//从   data[0]   ---> data[count-1]
	for(int i=0; i<pList->count; i++)
	{
		printf("%d ", pList->data[i]);
	}
	puts(" ");
	return OK;
}

6、顺序表的销毁

//函数功能:销毁顺序表
//函数参数:需要销毁的顺序表
//函数返回值:成功返回OK,失败返回失败原因
int Destory(List **pList)
{
	printf("D before:%p\n", *pList);
	//入参判断
	if(!*pList) return LIST_NULL;
	//销毁顺序表
	//1。存储空间
	free((*pList)->data);
	//2.销毁顺序表本表
	free(*pList);
	//3。NULL
	*pList = NULL;
	printf("D after:%p\n", *pList);
	return OK;
}

(二)带头结点的不循环单链表

1、结点定义

typedef int data_type;   //把int命名成data_type

//定义单链表表结点
typedef struct link
{
	/* member */
	data_type Data;
	struct link *Next;
}LNode;

2、创建链表

LNode *Create(void)
{
	//定义变量
	LNode *pLink = NULL;
	//开辟空间
	pLink = (LNode *)malloc(sizeof(LNode));
	//判断是否成功
	if(!pLink) return NULL;
	//清空
	memset(pLink, 0, sizeof(LNode));
	//返回
	return pLink;
}

3、插入结点

//函数功能:插入元素
//函数参数1:要插入元素的链表
//函数参数2:要插入的位置
//函数参数3:要插入的值
//函数返回值:成功返回OK,失败返回失败原因
int InsertItem(LNode *pHead, int pos, data_type item)
{
	LNode *pNew = NULL;//定义指向新结点的变量
	LNode *pTmp = pHead; //用来表示要插入位置的
	int i = 0; //表示移动的次数
	//入参判断
	if(!pHead) return LINK_NULL;
	//判断pos
	if(pos < -1) return POS_ERR;
	//创建结点
	pNew = Create();
	//给结点赋值
	pNew->Data = item;
	//插入
	if(-1 == pos) 
	{
		//尾插
		//1.找到为结点
		while(pTmp->Next != NULL)
		{
			//万后找
			pTmp = pTmp->Next;
		}
		//插入:
		pTmp->Next = pNew;
		return OK;
	}
	//中间插入
	//先找到要插入的位置:
	while(i < pos && pTmp != NULL)
	{
		//往后移动
		pTmp = pTmp->Next;
		//次数++
		i++;
	}
	if(pTmp == NULL)  return POS_ERR;
	//插入元素
	//1。保护后面所有结点
	pNew->Next = pTmp->Next;
	//2。插入元素
	pTmp->Next = pNew;
	//3。返回OK
	return OK;
}

4、显示链表

int ShowLink(LNode *pHead)
{
	LNode *pTmp = NULL;
	//入参盘算
	if(!pHead) return LINK_NULL;
	pTmp = pHead->Next;
	//2.打印,  从 首   打印到  为结点
	while(pTmp != NULL)
	{
		printf("%d ", pTmp->Data);
		//往后移动
		pTmp = pTmp->Next;
	}
	puts(" ");
	return OK;
}

5、删除元素

//函数功能:删除元素
//函数参数1:待删除元素的链表
//函数参数2:待删除的位置
//函数参数3:删除的值
//函数返回值:成功返回OK,失败返回失败原因
int DeleteItem(LNode *pHead, int pos, data_type *item)
{
	//定义变量,指向待删除的空间
	LNode *pDel = NULL;
	//指向操作删除的结点
	LNode *pDel_Pre = NULL;
	//定义表示移动次数的变量
	int i = 0;
	//入参判断
	if(!pHead) return LINK_NULL;
	//表为空
	if(!pHead->Next) return LINK_EMPTY;
	//pos错误:
	if(pos < -1) return POS_ERR;
	//初始值
	pDel = pHead->Next;
	pDel_Pre = pHead;
	//根据pos取先则删除的位置
	switch(pos)
	{
		case -1:
			//尾删
			//1.找到为结点
			while(pDel->Next != NULL)
			{
				//往后移动
				pDel = pDel->Next;
				pDel_Pre = pDel_Pre->Next;
			}
			//2.保存元素
			*item = pDel->Data;
			//3.删除结点
			pDel_Pre->Next = pDel->Next;
			break;
		case 0:
			//头删
			//1。保存元素
			*item = pDel->Data;
			//2.删除元素
			pDel_Pre->Next = pDel->Next;
			break;
		default:
			//中间删除
			//1.找到要删除的结点
			while(i < pos && pDel != NULL)
			{
				//往后移动
				pDel = pDel->Next;
				pDel_Pre = pDel_Pre->Next;
				//次数++
				i++;
			}
			if(pDel == NULL) return POS_ERR;
			//2.保存元素
			*item = pDel->Data;
			//3.删除元素
			pDel_Pre->Next = pDel->Next;
			break;
	}
	//释放空间
	free(pDel);
	return OK;
}
逆置一个单链表:
对原来的单链表采用头删,对新链表采用头插
逆着输出一个单链表:
循环单链表:
在循环链表下:尾结点的 Next 指向头节点

(三)双链表(带头结点的不循环双链表

1、结点定义

typedef int data_type;   //把int命名成data_type

//定义双链表表结点
typedef struct doublelinknode
{
	/* member */
	struct doublelinknode *Pre;
	data_type Data;
	struct doublelinknode *Next;
}DbLNode;

2、创建

DbLNode *Create(void)
{
	//定义变量
	DbLNode *pLink = NULL;
	//开辟空间
	pLink = (DbLNode *)malloc(sizeof(DbLNode));
	//判断是否成功
	if(!pLink) return NULL;
	//清空
	memset(pLink, 0, sizeof(DbLNode));
	//返回
	return pLink;
}

3、插入

//函数功能:给双链表插入元素
//函数参数1:待插入元素的双链表
//函数参数2:要插入的位置
//函数参数3:要插入的值
//函数返回值:成功返回OK,失败返回失败原因
int InsertItem(DbLNode *pHead, int pos, data_type item)
{
	DbLNode *pNew = NULL;
	DbLNode *pTmp = pHead;
	int i = 0;
	//入参判断
	if(!pHead)  return DBLINK_NULL;
	//poserr
	if(pos < -1)  return POS_ERR;
	//创建空间
	pNew = Create();
	//赋值
	pNew->Data = item;
	//插入
			//1.找到要插入的位置
			while(i<pos && pTmp != NULL)
			{
				//往后移动
				pTmp = pTmp->Next;
				i++;
			}
			if(NULL == pTmp) return POS_ERR;
			//插入
			pNew->Pre = pTmp;
			pNew->Next = pTmp->Next;
			if(NULL != pTmp->Next) 
			{
				pTmp->Next->Pre = pNew;
			}
			pTmp->Next = pNew;
	return OK;
}

4、删除

//函数功能:删除元素
//函数参数1:待删除元素的链表
//函数参数2:删除的位置
//函数参数3:保存删除的值
//函数返回值:成功返回OK。失败返回失败原因
int DeleteItem(DbLNode *pHead, int pos, data_type *item)
{
	int i = 0;
	DbLNode *pDel = NULL;  //定义变量
	DbLNode *pDel_Pre = pHead ; //初始化
	//入参判断
	if(!pHead) return DBLINK_NULL;
	//能否被删除元素
	if(NULL == pHead->Next)  return DBLINK_EMPTY;
	//判断能否执行
	if(pos < 0)  return  POS_ERR;
	//删除元素
	pDel = pHead->Next;
	//1。找到要删除的元素
	while(i<pos && pDel!=NULL)
	{
		//往后移动
		pDel = pDel->Next;
		pDel_Pre = pDel_Pre->Next;
		//自家
		i++;
	}
	if(pDel == NULL) return POS_ERR;
	//2.保存元素
	*item = pDel->Data;
	//3.删除
	if(pDel->Next != NULL) 
	{
		pDel->Next->Pre = pDel_Pre;
	}
	pDel_Pre->Next = pDel->Next;
	//4。释放空间
	free(pDel);
	return OK;
}

5、显示

int ShowList(DbLNode *pHead)
{
	DbLNode *pTmp = NULL;
	//入参判断
	if(!pHead) return DBLINK_NULL;
	pTmp = pHead->Next;//指向首届殿
	//从首届殿打印到为结点
	while(pTmp != NULL)
	{
		printf("%d ", pTmp->Data);
		pTmp = pTmp->Next;
	}
	printf("\n");
	return OK;
}
线性表,链表,顺序表之间的区别?
线性表是一种逻辑结构
顺序表和链表是线性表在顺序存储下和链式存储下的体现
链表和顺序表的区别:
1. 链表是链式存储,顺序表是顺序存储
2. 顺序表会有空间碎片产生,链表没有空间碎片产生
3. 链表一般多用于插入、删除较多的场景
顺序表一般用于查找较多的场景
4. 顺序表的存储空间利用率比链表的大
链表和顺序表怎么选择?
1. 从空间来说
顺序表对空间的要求比链表大
顺序表的利用率比链表大
2. 从操作来说
多用于查找,用顺序表
多用于插入、删除,用链表
3. 从编译环境来说
不支持指针类型操作的编译器不能使用链表
编译器不支持使用指针类型操作,场景又是多插入、删除操作
使用静态链表,本质是二维数组

(四)受限的线性表栈(受限在了操作上)

只允许数据在一段进行插入和删除操作,允许操作的一段叫做栈顶
特点:先进后出( FILO

1、栈的定义

//定义数据类型
typedef int data_type;
//栈的定义
typedef struct stack
{
data_type data[SIZE]; //存储空间
//只能在栈顶进行操作
int top; //指示栈顶
}Stack;

2、栈的创建

//函数功能:创建栈
//函数参数:void
//函数返回值:成功返回创建的首地址,失败返回NULL
Stack* CreateStack(void)
{
	//定义变量
	Stack *pStack = NULL; //初始化
	//申请空间
	if(NULL == (pStack = (Stack *)malloc(sizeof(Stack))))
	{
		perror("MALLOC_ERR");
		return NULL;
	}
	//清空
	memset(pStack, 0, sizeof(Stack));
	//返回
	return pStack;
}

3、入栈

//函数功能:入栈
//函数参数1:给谁入
//函数参数2:要赋值的值
//函数返回值:成功返回OK,失败返回失败原因
int Push(Stack *pStack, data_type item)
{
	//入参判断
	if(!pStack) return STACK_NULL;
	//判断是否能入栈(栈是否满)
	if(SIZE == pStack->top) return STACK_FULL;
	//入栈
	//先赋值,栈顶向上移动
	pStack->data[pStack->top] = item;
	pStack->top++;
	return OK;
}

4、出栈

//函数功能:出栈
//函数参数1:给谁出
//函数参数2:出的谁
//函数返回值:成功返回OK,失败返回失败原因
int Pop(Stack *pStack, data_type *item)
{
	//入参判断
	if(!pStack) return STACK_NULL;
	//判断是否能出栈(栈是否空)
	if(0 == pStack->top) return STACK_EMPTY;
	//出栈
	//先向下移动,再赋值
	pStack->top--;
	*item = pStack->data[pStack->top];
	return OK;
}

5、打印栈

//函数功能:显示栈
//函数参数:需要显示的栈
//函数返回值:成功返回OK,失败返回失败原因
int ShowItem(Stack *pStack)
{
	if(!pStack) return STACK_NULL;
	if(0 == pStack->top) return STACK_EMPTY;
	//从i=0打印到i=top-1
	for(int i = 0; i < pStack->top; i++)
	{
		printf("%d ", pStack->data[i]);
	}
	printf("\n");
	return OK;
}
链式存储:
线性表的链式存储是一样的
头是栈顶:头插、头删
尾是栈顶:尾插、尾删
操作的时候一般选择头作为操作
共享栈:

(五)队列(受限在了操作上

循环队列(解决普通队列的一次性问题)

只允许在一段执行插入操作,另一端执行删除操作,允许插入的一段叫做队尾,允许删除操作的一段叫做队头
特点:先进先出( FIFO
避免假队空的方法:
1. 舍弃一片空间 head = (tail+1)%SIZE
2. 另外设置一个变量 count == SIZE;
3. 设置标志位记录上次执行的操作是入队还是出队 head == tail && flag == 1
1、队列的定义
//定义数据类型
typedef int data_type;
//队列的定义
typedef struct queue
{
data_type data[SIZE]; //存储空间
//在队尾插入、队头删除
int tail; //指示队尾
int head; //指示队头
}Queue;

2、队列的创建
//函数功能:创建顺序队列
Queue *Create(void)
{
	Queue *pQueue = NULL;
	pQueue = (Queue *)malloc(sizeof(Queue));
	if(!pQueue) return NULL;
	memset(pQueue, 0, sizeof(Queue));
	return pQueue;
}

3、入队
//函数功能:插入元素
//函数参数:待插入元素的队列
//函数参数:需要插入的元素
//函数返回值:成功返回OK,失败返回失败原因
int InsertItem(Queue *pQueue, data_type item)
{
	//1.判断队列是否为NULL
	if(!pQueue) return QUEUE_NULL;
	//2.判断队列是否为满
	if((pQueue->tail+1)%SIZE == (pQueue->head)%SIZE)   return QUEUE_FULL;
	//3.入队
	pQueue->Data[pQueue->tail] = item;
	//3。1赋值
	pQueue->tail = (pQueue->tail + 1)%SIZE;
	//3。2自加
	return OK;
}

4、出队
//函数功能:删除元素
//函数参数:待删除元素的队列
//函数参数:删除的元素
//函数返回值:成功返回OK,失败返回失败原因
int DeleteItem(Queue *pQueue, data_type *item)
{

	//1.判断队列是否为NULL
	if(!pQueue) return QUEUE_NULL;
	//2.判断队列是否为空
	if(pQueue->tail == pQueue->head) return QUEUE_EMPTY;
	//3.出队
	//3。1赋值
	*item = pQueue->Data[pQueue->head];
	//3。2自加
	pQueue->head = (pQueue->head + 1)%SIZE;
	return OK;
}

5、显示队列

//函数功能:显示队列
//函数参数:需要显示的队列
//函数返回值:成功返回OK,失败返回失败原因
int ShowQueue(Queue *pQueue)
{
	//判断队列是否为NULL
	if(!pQueue) return QUEUE_NULL;
	//判断队列是否为空
	if(pQueue->tail == pQueue->head) return QUEUE_EMPTY;
	//打印
	for(int i=0;i<SIZE;i++)
	{
		//队没有满打印完了退出,不继续
        if((pQueue->head+i)%SIZE == pQueue->tail )	break;
		printf("%d ",pQueue->Data[(pQueue->head+i)%SIZE]); 
	}
	printf("\n");
	return OK;
}

(六)什么时候考虑用栈?

入的顺序和出的顺序发生变化

栈的应用

1.printf 函数的右结合
2. 函数栈
3. 中缀表达式 (a+b)/c a+b/c 后缀表达式: ab+c/ abc/+
判断一个表达式中的括号是否匹配?
判断一个单链表的元素是否关于中心对称?
KMP 算法:

四、树

(一)什么是树?

树( Tree )是 n n≥0 )个节点的有限集合 T ,它满足两个条件 :
有且仅有一个特定的称为根( Root )的节点;
其余的节点可以分为 m m≥0 )个互不相交的有限集合 T1 T2 …… Tm ,其中每一个集合又是一棵树,并称为其根
的子树( Subtree )。
树的相关概念
度数:
一个节点的子树的个数称为该节点的度数,一棵树的度数是指该树中节点的最大度数。
高度(层数、深度):
节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。
路径:边数:
一个节点系列 k1,k2, ……,ki,ki+1, ……,kj, 并满足 ki ki+1 的父节点,就称为一条从 k1 kj 的路径,路径的
长度为 j-1, 即路径中的边数。路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。
叶子节点:
度数为 0 的结点

(二)二叉树

二叉树的定义 :二叉树( Binary Tree )是 n n≥0 )个节点的有限集合,它或者是空集( n 0 ),或者是由一个
根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分
左孩子和右孩子,即使只有一个子节点也要区分左右。
满二叉树:有k层,恰好有2 k -1个结点
完全二叉树:在满二叉树的基础上,从右往左,从下往上依次去除结点
平衡二叉树:
每一个结点的左子树的层数和右子树的层数不超过 2
目的:解决树的降维问题(把树将成链表),提高效率
赫夫曼树(哈夫曼树、最优二叉树):
赫夫曼 (Huffman) 树,又称最优树,是带权路径长度最短的树,有着广泛的应用
从树中一个结点到另外一个结点的分支构成一条路径,分支的数目称为路径的长度。树的路径长度是指从树根到每
个结点的路径长度之和
进一步推广,考虑带权的结点。结点的带权路径长度指的是从树根到该结点的路径长度和结点上权的乘积。树的带
权路径长度是指所有叶子节点的带权路径长度之和,记作 WPL WPL 最小的二叉树就是最优二叉树,又称为赫夫曼
线索二叉树:

1、树的顺序存储

2、树形结构的链式存储:

(1)树结点的定义

typedef int data_type;

//定义链表结点数据类型
typedef struct bintreenode
{
	/* member */
	struct bintreenode *Left;
	data_type Data;
	struct bintreenode *Right;
}BTree;

(2)树结点的创建

//函数功能:申请空间
BTree *Creat(void)
{
	//定义变量
	BTree *pTree = NULL;
	//申请空间
	pTree = (BTree *)malloc(sizeof(BTree));
	if(!pTree) return NULL;
	//清空空间
	memset(pTree, 0, sizeof(BTree));
	//成功返回
	return pTree;
}

(3)树结点的插入

int InsertItem(BTree *pTree, data_type item, int *Flag)
{
	BTree *pNew = NULL;  //指示新申请的空间
	BTree *pTmp = pTree;  //指示插入位置
	//入参判断
	if(!pTree) return TREE_NULL;
	//判断是否为首次插入
	if(*Flag)
	{
		//直接将要插入的值,赋值给Data域
		pTree->Data = item;
		//把标志位置空
		*Flag = 0;
		return OK;
	}
	//其余次 插入
	//1。申请空间
	pNew = Creat();
	//2。赋值给
	pNew->Data = item;
	//3。找到要插入的位置
	while(1)
	{
		if(item <= pTmp->Data)
		{
			//在左边
			if( NULL == pTmp->Left)
			{
				//找到了,插入
				pTmp->Left = pNew;
				return OK;
			}
			//往左子树跑
			pTmp = pTmp->Left;
		}
		else
		{
			//在右边
			if( NULL == pTmp->Right)
			{
				//插入
				pTmp->Right = pNew;
				return OK;
			}
			//往右跑
			pTmp = pTmp->Right;
		}
	}
}

(4)树结点的遍历

void Fri_Travel(BTree *pTree)
{
	//退出条件
	if(NULL == pTree) return ;
	//先访问根结点
	printf("%d ", pTree->Data);
	//以先序便利的形式,访问左子树
	Fri_Travel(pTree->Left);
	//以先序便利的形式,遍历右子树
	Fri_Travel(pTree->Right);
}

void Mid_Travel(BTree *pTree)
{
	//退出条件
	if(NULL == pTree) return ;
	//先遍历左子树
	Mid_Travel(pTree->Left);
	printf("%d ", pTree->Data);
	Mid_Travel(pTree->Right);
}

void Fin_Travel(BTree *pTree)
{
	if(NULL == pTree) return;
	Fin_Travel(pTree->Left);
	Fin_Travel(pTree->Right);
	printf("%d ", pTree->Data);
}
(5)树结点的删除
待删除结点没有孩子,直接删除
待删除结点有一个孩子,子承父位
待删除结点有两个孩子,找右子树中最小的值或者左子树中最大的值来继承
深度优先:
先序遍历,后序遍历
广度优先:
层次遍历
队列

 

五、网状结构

分类:
按有无方向可以分为:有向图、无向图
按是否带权值:带权图和不带权图
网状结构的顺序存储:
利用数组存储
网状结构的链式存储

有向图:十字链表法

无向图:多重链表

 

对于图的知识点:
最短路径算法 - 迪杰斯特拉 (Dijkstra) 算法
弗洛伊德( Floyd )算法求图的最短路径

六、算法

算法是有限指令的有序集合。
算法是有穷的,程序是无穷的
程序 = 算法 + 数据结构

(一)算法的特征:

有穷性:算法必须在有限个语句能描述完
确定性(无二义性):每一条语句只能有一个解释
可行性:能运行的
输入:
输出:

(二)怎么样评判算法的好坏:

效率与低存储量需求(时间复杂度,空间复杂度):
时间复杂度:执行这个算法需要花费多少时间。
eg:
int sum = 0;
for(int i = 0; i < n; i++)
{
sum += i;
}
printf("%d\n", sum);
T(n) = n + 2
O(n) = n
只需要记录量级 1
1. 顺序执行的代码,只会影响常数项,可以忽略 O(1)
2. 只需要挑选循环中的一个基本操作来分析他的执行步骤的次数与 n (问题规模)的关系。
3. 如果有多层循环嵌套,只需要关注最深层次循环的个数。
优化时间复杂度:
首先得保证算法的正确性,在这个基础上,思考怎么去减少循环的使用。
空间复杂度:执行这个代码,需要花费多少空间。
设算法对应问题的规模为 n, 执行算法所占存储空间的量级为 D(n) ,则 D(n) 为算法的空间复杂度。
优化空间复杂度:
1. 在定义的时候,更少的使用空间;
1. 字节对齐:
2. 位域:
2. 在执行的时候,尽量避免开辟不必要的空间,并且功能结束之后,释放掉不用的空
间。
正确性:算法得无误运行。
可读性、可维护性:对人的友好,是从编程规范入手优化。
健壮性、鲁棒性:算法在输入有误的信息的时候,代码还能够按照预想的方式执行,不出现 BUG ,不会退出。

(三)常见的查找算法

1、线性结构:
顺序查找:遍历
折半查找:二分查找
限制:必须有序、必须是一个顺序表
分块查找:块间有序,块内无序
2、树状结构:
二叉排序树:二叉树的查找
二叉平衡树:避免树降维成链表
遍历:
先序遍历、中序遍历、后续遍历、广度有限、深度优先
3、散列表(哈希表):
对给定的 k ,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级 O(C) 。这就要求在建立记录表
的时候,确定记录的 key 与其存储地址之间的关系 f ,即使 key 与记录的存放地址 H 相对应
根据要找的 key 关键字,直接能找到数据的记录本身
(1)构建哈希函数:
直接地址法,平方取中法,叠加法、质数除余法
(2) 冲突:
什么是冲突:冲突是指:表中某地址 j [0 m-1] 中己存放有记录,而另一个记录的 H(key) 值也为 j
怎么解决冲突:
开放地址法:
不太好,因为会将哈希表退化成线性表,还只能使用顺序查找。
再次哈希法:
也不好,因为麻烦
链地址法

(四)常见的排序算法

1、内部排序

(插入)

直接插入:重新构建一个链表

折半插入:和二分查找类似

希尔排序:增量,逐渐减少的,直到增量为 1 为止

(交换)

冒泡

每一次运行总会将最小的或者最大的放到前面,如果需要交换,一直在交换

快速排序*:

选择

简单选择:每一次运行总会将最小的或者最大的放到前面,最后只交换一次
堆(大根堆,小根堆):根 >= 左右孩子的,牵扯到树的变化,所以只有特定场景下才使用
(多路归并)
归并排序

 (基数排序)

2、外部排序
基于多路归并的外部排序:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值