1.数据结构与算法概述
1.1数据结构定义
把现实中大量而复杂的问题,以特定的数据类型(个体)和特定的存储结构(个体之间的关系)保存到主存储器(内存)中,以及在此基础之上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也叫做算法。
数据结构 = 个体 + 个体之间的关系
算法 = 对存储数据的操作
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
1.2算法概念
算法(algorithm)是指在解决问题时,按照某种机械的步骤一定可以得到问题的结果(有的问题有解,有的没有)的处理过程。算法就是解决这个问题的方法和步骤的描述。
1.2.1衡量算法的标准
1.时间复杂度:大概程序要执行的次数,而非执行的时间
2.空间复杂度:算法执行过程中大概所占用的最大内存
3.难易程度:算法的难易程度
4.健壮性:算法在遇到错误或崩溃时的容错性
1.3数据的存储结构
1.3.1逻辑结构
所谓逻辑结构就是数据与数据之间的关联关系,准确的说是数据元素之间的关联关系。
注:所有的数据都是由数据元素构成,数据元素是数据的基本构成单位。而数据元素由多个数据项构成。
逻辑结构有四种基本类型:集合结构、线性结构、树状结构和网络结构。也可以统一的分为线性结构和非线性结构。
四种基本类型:
(1)集合结构 : 集合结构中的元素关系,除了同属于一个集合这个关系以外,再无其他关系。
(2)线性结构:线性结构中,元素间的关系就是一对一,顾名思义,一条线性的结构。
(3)树形结构:树形结构中,元素间的关系就是一对多,一颗大叔,伸展出的枝叶,也是类金字塔形。
(4)图形结构:图形结构中,元素间的关系就是多对多,举例:一个人可以通过6个人间接认识到世界上的每一个人。类蛛网形。
统一划分:
线性结构:数组、链表 注:栈和队列是特殊的线性结构
非线性结构:树、图
1.3.2物理结构
数据的物理结构就是数据存储在磁盘中的方式。官方语言为:数据结构在计算机中的表示(又称映像)称为数据的物理结构,或称存储结构。它所研究的是数据结构在计算机中的实现方法,包括数据结构中元素的表示及元素间关系的表示。
物理结构一般有四种:顺序存储,链式存储,散列,索引
1.4数据结构的重要性
数据结构是编程中的核心
2.数据结构分类:
2.1线性结构(把所有的结点用一根直线穿起来)
2.1.1连续存储 [数组]
数组中元素类型相同,大小相等
优点:
存取速度快效率高
缺点:
需要占用大块连续且连续的内存
在分配内存前必须指定数组的长度
插入和删除元素效率很低
数组的常用操作(C++)
#include <iostream>
using namespace std;
//定义数组结构体
struct Arr
{
int* pBase; //存储数组第一个元素的地址
int len; //存储数组的长度
int cnt; //存储数组当前有效元素个数
};
void init_arr(Arr* pArr, int length); //初始化数组
bool append_arr(Arr* pArr, int value); //对数组进行追加新值
bool insert_arr(Arr* pArr, int pos, int value);//在pos位置前插入新的数组元素(pos从1开始)
bool delete_arr(Arr* pArr, int pos,int * pVal);//删除pos位置的元素,并返回被删除的元素(pos从1开始)
int get(Arr* pArr, int pos); //返回指定位置的元素值(pos从1开始)
bool is_myempty(Arr* pArr); //判断数组是否为空
bool is_full(Arr* pArr); //判断数组是否已满
void sort_arr(Arr*pArr); //升序排列
void show_arr(Arr * pArr); //遍历数组
void inversion_arr(Arr*pArr); // 倒置数组
int main()
{
Arr arr;
int val;
init_arr(&arr,6);
append_arr(&arr, 1);
append_arr(&arr, 2);
append_arr(&arr, 3);
append_arr(&arr, 4);
append_arr(&arr, 5);
inversion_arr(&arr);
sort_arr(&arr);
show_arr(&arr);
cout << get(&arr, 4) << endl;;
return 0;
}
// 创建数组
void init_arr(Arr * pArr,int length)
{
pArr->pBase = (int*)malloc(sizeof(int) * length); //为数组首地址动态分配内存
if (pArr->pBase == NULL)
{
cout << "动态内存分配失败!" << endl;
exit(-1);
}
else
{
pArr->cnt = 0;
pArr->len = length;
}
return;
}
// 判断数组是否为空
bool is_myempty(Arr* pArr)
{
if (pArr->cnt == 0)
return true;
else
return false;
}
// 遍历数组
void show_arr(Arr* pArr)
{
if (is_myempty(pArr))
{
cout << "数组为空!" << endl;
}
else
{
for (int i = 0; i < pArr->cnt; i++)
{
cout << "a["<< i<<"]=" << *(pArr->pBase + i) << endl;
}
}
}
// 追加数据
bool append_arr(Arr* pArr, int value)
{
if (is_full(pArr))
{
cout << "添加失败!数组已满!" << endl;
return false;
}
else
{
pArr->pBase[pArr->cnt] = value;
(pArr->cnt)++;
return true;
}
}
// 判断数组是否已满
bool is_full(Arr* pArr)
{
if (pArr->cnt == pArr->len)
return true;
else
return false;
}
//插入新的数组元素(pos从1开始)
bool insert_arr(Arr* pArr, int pos, int value)
{
if (is_full(pArr))
return false;
if (pos<1 || pos>pArr->cnt + 1)
return false;
for (int i = pArr->cnt - 1; i >= pos - 1; i--)
{
pArr->pBase[i + 1] = pArr->pBase[i];
}
pArr->pBase[pos - 1] = value;
pArr->cnt++;
return true;
}
//删除
bool delete_arr(Arr* pArr, int pos, int* pVal)
{
if (is_full(pArr))
return false;
if (pos<1 || pos>pArr->cnt)
return false;
*pVal = pArr->pBase[pos - 1];
for (int i = pos; i <pArr->cnt; i++)
{
pArr->pBase[i -1] = pArr->pBase[i];
}
pArr->cnt--;
return true;
}
//倒置数组
void inversion_arr(Arr* pArr)
{
int i = 0;
int j = pArr->cnt - 1;
int temp;
while (i < j)
{
temp = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = temp;
i++;
j--;
}
return ;
}
//升序排列
void sort_arr(Arr* pArr)
{
int i, j,temp;
for (i = 0; i < pArr->cnt; i++)
{
for (j = i + 1; j < pArr->cnt; j++)
{
if (pArr->pBase[i] > pArr->pBase[j])
{
temp = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = temp;
}
}
}
}
//返回指定位置的元素值
int get(Arr* pArr, int pos)
{
return*(pArr->pBase + pos - 1);
}
2.1.2离散存储 [链表]
特点:
1)n个结点离散分配
2)彼此通过指针相连
3)每个结点只有一个前驱结点,每个结点只有一个后续结点
4)首结点没有前驱结点,尾结点没有后续结点
优点:
空间没有限制,插入和删除元素速度快
缺点:
存取的速度慢
常用术语:
首结点:第一个有效结点
尾结点:最后一个有效结点
头结点:首结点之前的结点,数据类型与首结点相同。注:头结点不存放有效数据,目的是为了方面对链表的操作
头指针:指向头结点的指针变量(存放头结点的地址)
尾指针 :指向尾结点的指针变量(存放尾结点的地址)
确定链表需要的参数
头指针
链表的分类
单链表
双链表:每个结点有两个指针域
循环链表:能通过任何一个结点找到其他所有的结点
非循环链表
链表常用算法(C++):
#include <iostream>
using namespace std;
//定义结点结构体
typedef struct Node
{
int data; //数据域,存放结点中的数据
Node* pNext; //指针域,指向下一个结点的指针
}NODE,* PNODE; //NODE等价Node类型(表示结点),pNODE等价于Node*(表示结点的地址)
PNODE create_list(); //创建链表,并返回链表的头结点的地址
void traverse_list(PNODE pHead); //遍历链表
bool is_empty(PNODE pHead); //判断链表是否为空
int length_list(PNODE pHead); //求链表长度
bool insert_list(PNODE pHead, int pos, int value); //把指定结点值插入到指定位置(pos的值从1开始)
bool delete_list(PNODE pHead, int pos, int* value); //删除指定位置的结点,并返回删除值(pos的值从1开始)
void sort_list(PNODE pHead); //对链表进行升序排列
int main()
{
PNODE pHead = NULL; //创建一个头指针
pHead = create_list(); //创建一个非循环单链表,并将链表的头结点地址赋给头指针
traverse_list(pHead); //遍历链表
cout << length_list(pHead) << endl;//输出链表的长度
sort_list(pHead);
cout << endl;
insert_list(pHead, 1, 99);
int d;
delete_list(pHead, 3, &d);
cout << d << endl;
traverse_list(pHead);
return 0;
}
//创建链表
PNODE create_list()
{
int len;
int val;
cout << "请输入您要生成链表结点的个数:" << endl;
cin >> len;
PNODE pHead = (PNODE)malloc(sizeof(NODE)); //为头结点分配内存(不存放有效数据,目前数据域为空,指针域为空)
PNODE pTail = pHead; //初始化一个尾结点,地址等于头结点地址
pTail->pNext = NULL; //初始化尾结点的指针域为空
if (pHead == NULL)
{
cout << "内存分配失败,程序终止!";
exit(-1);
}
for (int i = 0; i < len; i++)
{
cout << "请输入第"<<i+1<<"个结点的值:" << endl;
cin >> val;
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pNew == NULL)
{
cout << "内存分配失败,程序终止!";
exit(-1);
}
pNew->data = val; //定义新结点的数据域
pTail->pNext = pNew; //使尾结点的指针域指向新结点
pNew->pNext = NULL; //使新结点指向空
pTail = pNew; //尾结点时刻指向创建的新结点
}
return pHead;
}
//遍历链表
void traverse_list(PNODE pHead)
{
PNODE p = pHead->pNext; //创建首结点
while (p!=NULL)
{
cout << p->data <<"\t";
p = p->pNext;
}
return;
}
//判断链表是否为空
bool is_empty(PNODE pHead)
{
if (pHead->pNext == NULL)
return true;
else
return false;
}
//求链表长度
int length_list(PNODE pHead)
{
int length = 0;
PNODE p = pHead->pNext; //创建首结点
while (p != NULL)
{
length++;
p = p->pNext;
}
return length;
}
//对链表进行升序操作
void sort_list(PNODE pHead)
{
int len = length_list(pHead);
PNODE p, q; //用于交换data值
int i, j, t; //ij用于计数,t用于传值
for(i = 0,p = pHead->pNext;i<len-1;i++,p=p->pNext)
for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
{
if (p->data > q->data)
{
t = p->data;
p->data = q->data;
q->data = t;
}
}
}
// 插入操作
bool insert_list(PNODE pHead, int pos, int value)
{
int i = 0;
PNODE p = pHead;
while (p != NULL && pos - 1 > i) //当头指针存在时定义pos-1<i的作用是使p指向所插入位置的前一个结点
{
p = p->pNext;
i++;
}
if (pos - 1 < i || p == NULL)
return false;
PNODE pNew = (PNODE)malloc(sizeof(NODE));//创建新的结点
if (pNew == NULL)
{
cout << "内存分配失败,程序终止!";
exit(-1);
}
pNew->data = value; //对新的结点赋值
PNODE q = p->pNext; //定义临时结点q等于插入位置结点
p->pNext = pNew; //插入位置前的结点指向下一个结点(即插入位置结点)等于新的结点
pNew->pNext = q; //新结点的下一个结点等于插入位置结点(插入后即为插入位置的下一个结点)
}
//删除操作
bool delete_list(PNODE pHead, int pos, int* value)
{
int i = 0;
PNODE p = pHead;
while (p->pNext != NULL && pos-1 > i) //当所要删除的结点不为空时,定义pos-1<i的作用是使p指向所删除位置的前一个结点
{
p = p->pNext;
i++;
}
if (pos-1 < i || p->pNext == NULL)
return false;
PNODE q = p->pNext;
*value = q->data;
p->pNext = q->pNext;
free (q);
q = NULL;
}
算法:
狭义的算法与数据的存储方式密切相关
广义的算法与数据的存储方式无关
泛型:
利用某种技术达到的效果:不同的存储方式,执行的操作是一样的
2.2线性结构的常见应用之一-----栈
定义:一种可以实现“先进后出”的存储结构,只能在栈顶出入,由栈顶指向栈底(从栈顶开始访问)。
分类:
1)静态栈(连续)
类似于数组的结构关系
2)动态栈(不连续)
即链式栈
栈常用算法(C++):
#include <iostream>
using namespace std;
//创建结构体用于存储结点信息
typedef struct Node
{
int data; //数据域,存储结点数据
Node* pNext; //指针域,存储下一个结点地址
}NODE, * PNODE;
//创建结构体用于存储栈的顶和底
typedef struct Stack
{
PNODE pTop; //指向栈顶
PNODE pBottom; //指向栈底
}STACK,* PSTACK;
void init_Stack(PSTACK pS); //初始化一个空栈
bool push_Stack(PSTACK pS,int value); //压栈
void traverse_Stack(PSTACK pS); //遍历栈
bool pop(PSTACK pS, int* pValue); //出栈
bool is_empt(PSTACK pS); //判断栈是否为空
void clear(PSTACK pS); //清空栈中的有效数据,变成空栈
int main()
{
STACK S;
int value; //存放出栈的元素值
init_Stack(&S);
push_Stack(&S, 5);
push_Stack(&S, 4);
push_Stack(&S, 3);
push_Stack(&S, 2);
push_Stack(&S, 415);
traverse_Stack(&S);
clear(&S);
traverse_Stack(&S);
return 0;
}
// 初始化栈
void init_Stack(PSTACK pS)
{
pS->pTop = (PNODE)malloc(sizeof(NODE)); //分配无有效值的结点(头结点)内存,并把地址传给栈顶指针
if (pS->pTop == NULL)
{
cout << "动态内存分配失败,程序已退出!" << endl;
exit(-1);
}
else
{
pS->pBottom = pS->pTop; //初始化时栈顶指针与栈底指针都指向头结点
pS->pTop->pNext = NULL;
}
return;
}
//判断栈是否为空
bool is_empt(PSTACK pS)
{
if (pS->pBottom == pS->pTop)
return true;
else
return false;
}
//压栈操作
bool push_Stack(PSTACK pS, int value)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (pS->pTop == NULL)
{
cout << "动态内存分配失败,程序已退出!" << endl;
exit(-1);
}
else
{
pNew->data = value;
pNew->pNext = pS->pTop;
pS->pTop = pNew;
}
return true;
}
//遍历操作
void traverse_Stack(PSTACK pS)
{
PNODE p = pS->pTop;
while (p != pS->pBottom)
{
cout << p->data << "\t";
p = p->pNext;
}
return;
}
//出栈操作
bool pop(PSTACK pS, int* pValue)
{
if (is_empt(pS))
{
return false;
}
else
{
PNODE q = pS->pTop;
*pValue = q->data;
pS->pTop = q->pNext;
free(q);
q = NULL;
return true;
}
}
//清空操作
void clear(PSTACK pS)
{
if (is_empt(pS))
{
return;
}
else
{
PNODE p = pS->pTop;
PNODE q = NULL;
while (p != pS->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
pS->pBottom = pS->pTop;
}
}
栈的应用:
1)函数的嵌套调用(利用了栈的压栈和出栈)
2)中断
3)表达式求值
4)内存分配
5)缓存处理
6)迷宫
2.3线性结构的常见应用之二-----队列
定义:一种可以实现“先进先出 出”的存储结构,队首出队尾入,由队首指向队尾(从队首开始访问)。
分类:
1)静态队列(用数组实现)
静态队列必须是循环队列
循环队列相关问题:
- 静态队列为什么必须是循环队列?
因为静态队列是基于数组实现的,如果不用循环队列,会导致删除的元素所使用的空间无法继续使用,造成空间的浪费。 - 循环队列需要几个参数来确定?
需要两个参数确定:front表示队头;rear表示队尾 - 循环队列各个参数的含义?
注意:上面两个参数在下面不同的场合有不同的含义
1)队列初始时
front与rear的值都是零
2)队列非空时
front代表队列的第一个元素
rear代表队列最后一个有效元素的下一个元素(不存放有效值)
3)队列为空时
front等于rear,但其值不一定为零 - 循环队列入队伪算法?
第一步:将入队的值存入rear所表示的位置
第二步:更新rear的值:rear = (rear+1)%队列长度 - 循环队列出队伪算法?
直接更新front的值:front = (front+1)%队列长度。 - 如何判断循环队列是否为空?
如果front与rear的值相等,则队列为空。 - 如何判断循环队列是否已满?
两种方式:
1)增加一个标志参数
用参数记录当前有效元素的个数
2)少用一个元素位置(n个位置只用n-1个)
if((r+1)%队列长度==f)
队列已满
else
队列未满
2)链式队列(用链表实现)
循环队列的常用算法(C++):
#include <iostream>
using namespace std;
//定义队列结构体
typedef struct Queue
{
int* pBase; //数组的首地址
int front; //存储队首下标
int rear; //存储队尾下标
}QUEUE;
void init_queue(QUEUE*pQ); //初始化队列
bool en_queue(QUEUE* pQ, int value); //入队
void traverse_queue(QUEUE* pQ); //遍历队列
bool is_full(QUEUE* pQ); //判断队列是否已满
bool is_empt(QUEUE* pQ); //判断队列是否为空
bool out_queue(QUEUE* pQ, int* pValue); //出栈,并返回出栈元素
int main()
{
QUEUE Q;
int value;
init_queue(&Q);
en_queue(&Q, 1);
en_queue(&Q, 2);
en_queue(&Q, 3);
en_queue(&Q, 4);
en_queue(&Q, 5);
traverse_queue(&Q);
out_queue(&Q, &value);
cout << value << endl;
traverse_queue(&Q);
return 0;
}
//初始化队列
void init_queue(QUEUE* pQ)
{
pQ->pBase = (int*)malloc(sizeof(int) * 6); //创建数组长度为6
pQ->front = 0;
pQ->rear = 0;
}
//判断队列是否已满
bool is_full(QUEUE* pQ)
{
if ((pQ->rear + 1) % 6 == pQ->front)
return true;
else
return false;
}
//入队
bool en_queue(QUEUE* pQ, int value)
{
if (is_full(pQ))
return false;
else
{
pQ->pBase[pQ->rear] = value;
pQ->rear = (pQ->rear + 1) % 6;
return true;
}
}
//遍历队列
void traverse_queue(QUEUE* pQ)
{
int i = pQ->front;
while (i != pQ->rear)
{
cout << pQ->pBase[i] << "\t";
i = (i + 1) % 6;
}
cout << endl;
return;
}
//判断队列是否为空
bool is_empt(QUEUE* pQ)
{
if (pQ->front == pQ->rear)
return true;
else
return false;
}
//出队
bool out_queue(QUEUE* pQ, int* pValue)
{
if (is_empt(pQ))
return false;
else
{
*pValue = pQ->pBase[pQ->front];
pQ->front = (pQ->front + 1) % 6;
return true;
}
}
队列的应用:
所有与时间有关的操作都与队列有关(顺序操作)
3.递归概述
定义:一个函数直接或间接的调用本身
关于函数的调用:
1、当一个函数运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成三件事:
1)将所有的实际参数,返回该函数的地址等信息传递给被调用的函数保存
2)为被调用函数的局部变量(包括形参)分配存储空间
3)将控制转移到被调用函数的入口
2、从被调用函数返回到主调函数之前,系统需要完成的三件事:
1)保存被调用函数的返回结果
2)释放被调用函数所占用的存储空间
3)根据被调用函数之前所保存的主函数地址将控制转移到主调函数
3、当有多个函数相互调用时,按照“后调用的函数先返回先释放空间”的原则,上述函数之间的信息 传递和控制转移必须借助“栈”来实现,系统将整个程序运行时所需要的数据空间安排在一个栈中, 每当调用一个函数时,就在栈顶分配一个存储区域,进行压栈操作,每当一个函数退出时,从栈顶 释放它的存储空间,进行出栈操作,当前运行的函数永远都在栈顶位置。
递归需要满足的三个条件:
1、递归必须得有一个明确的中止条件
2、递归的函数所处理的数据规模必须在递减
3、问题转化到递归后必须时可解的
循环与递归的比较
循环:
优点:速度快、占用存储空间小
缺点:不易被人理解
递归:
优点:易于理解
缺点:速度慢、占用存储空间大
程序举例:(求和、求阶乘、汉诺塔、斐波拉契数列)
#include <iostream>
using namespace std;
//斐波拉契序列
int feibolaxi(int n)
{
if (n == 1|| n==0)
return 1;
else
return feibolaxi(n - 1)+feibolaxi(n-2);
}
//求和
int qiuhe(int n)
{
if (n == 1)
return 1;
else
return qiuhe(n - 1) + n;
}
//求阶乘值
int jiecheng(int n)
{
if (n == 1)
return 1;
else
return jiecheng(n - 1)*n;
}
//求汉诺塔需要移动的次数
int hannuota_count (int n)
{
if (n == 1)
return 1;
else
return 2*hannuota_count(n - 1)+1 ;
}
//汉诺塔问题的伪算法
/*
设第一个柱子为A第二个为B第三个为C,有n个盘子
思路:
1、先把A柱子上的前n-1个盘子从A借助C移动到B
2、在将A柱子上的第n个盘子直接从A移动到C
3、最后将B柱子上的n-1个盘子借助A移动到C
*/
void hannuota(int n, char A, char B, char C)
{
if (n == 1) //如果只有一个盘子直接将A柱子上的盘子从A移到C
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()
{
while (1)
{
int n;
cout << "请输入您要计算的阶乘数值:" << endl;
cin >> n;
cout << "结果为:" << jiecheng(n) << endl;
cout << "请输入您要求和的数值:" << endl;
cin >> n;
cout << "结果为:" << qiuhe(n) << endl;
cout << "请输入您要计算的汉诺塔的叠数:" << endl;
cin >> n;
cout << "结果为:" << hannuota_count(n) << endl;
cout << "请输入您要计算的汉诺塔的叠数:" << endl;
cin >> n;
cout << "结果为:" << feibolaxi( n) << endl;
}
return 0;
}
递归的应用
树和图的算法、数学方式(例如:斐波那契数列)
4.非线性结构
4.1树
4.1.1树的定义
树是由n(n≥1)个有限节点组成一个具有层次关系的[集合]。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
4.1.2树的特点
- 一棵树有且只有一个称为根的结点
- 一棵树有若干个互不相交的子树,这些子树本身也是一棵树
- 一棵树由结点和边组成
- 每个结点只有一个父结点,但可以由多个子结点
- 根节点没有父节点
4.1.3专业术语:
深度:从根节点开始到最底层结点的层数称为深度(根节点是第一层)
叶子结点:没有子节点的结点
非终端结点(非叶子结点)
度:子节点的个数称为度
4.1.4树的分类:
一般树:任意一个结点的子节点个数都不受限制
二叉树:任意一个结点的子结点的个数最多为两个,且子节点的位置不可更改(左右顺序:左子树 右子树是有序的),所以二叉树为有序树
二叉树的分类: 1)一般二叉树
2)满二叉树:在不增加树的层数的前提下,无法再多添加一个结点的二叉树称 为满二叉树
3)完全二叉树:如果只删除了满二叉树最底层最右边的连续若干个结点,这样 形成的二叉树就是完全二叉树(满二叉树是完全二叉树的一个 特例)
森林:n个互不相交的树的集合
4.1.5树的存储
思想:把非线性结构的树转化为线性结构存储
二叉树的存储:
1.连续存储【完全二叉树】
优点:查找某个结点的父节点和子节点速度快
缺点:占用内存空间过大
2.链式存储
一般树的存储:
1.双亲表示法 //易于求父节点
2.孩子表示法 //易于求子结点
3.双亲孩子表示法 //结合上面两个的优点
4.二叉树表示法 //把一个普通树转化为二叉树存储(方法:把任意一个结点的左指针域指向 第一个孩子,右指针域指向它的兄弟) 注:一个普通的树转换成二叉树一 定没有右子树。
4.1.6森林的存储
同样也是转化为二叉树来存储
方法与普通树转化为二叉树相似,不同的是不同的树之间的关系为兄弟关系,如下图。
4.1.7树的常见操作
1.二叉树的遍历
先序遍历[先访问根节点]:
含义:先访问根节点,再先序遍历左子树,最后先序遍历右子树。
中序遍历[中间访问根节点]:
含义:先中序遍历左子树,再访问根节点,最后中序遍历右子树。
后序遍历[最后访问根节点]:
含义:先后序遍历左子树,再后序遍历右子树,最后访问根节点。
4.1.8已知两种遍历序列求原始二叉树
已知先序和中序或已知中序和后序,可以还原原始二叉树。而已知先序和后序无法确定
1.已知先序和中序求后序遍历序列:
例一:
例二:
2.已知后序和中序求先序遍历序列:
3.二叉树三种遍历的代码实现(C++)
#include <iostream>
using namespace std;
//定义结构体存储结点信息
typedef struct BTNode
{
char data; //结点的数据域
struct BTNode* pLchild; //结点的左值针(指向左孩子)
struct BTNode* pRchild; //结点的右值针(指向右孩子)
}BTNODE,*pBTNODE;
pBTNODE create_BTree(); //创建二叉树并返回根节点地址
void PreTraverseBTree(pBTNODE pT); //先序遍历二叉树
void InTraverseBTree(pBTNODE pT); //中序遍历二叉树
void PosTraverseBTree(pBTNODE pT); //后序遍历二叉树
int main()
{
pBTNODE pT = create_BTree();
PreTraverseBTree(pT);
cout << endl;
InTraverseBTree(pT);
cout << endl;
PosTraverseBTree(pT);
return 0;
}
//创建二叉树
pBTNODE create_BTree()
{
pBTNODE pA = (pBTNODE)malloc(sizeof(BTNODE));
pBTNODE pB = (pBTNODE)malloc(sizeof(BTNODE));
pBTNODE pC = (pBTNODE)malloc(sizeof(BTNODE));
pBTNODE pD = (pBTNODE)malloc(sizeof(BTNODE));
pBTNODE pE = (pBTNODE)malloc(sizeof(BTNODE));
pA->data = 'A';
pB->data = 'B';
pC->data = 'C';
pD->data = 'D';
pE->data = 'E';
pA->pLchild = pB;
pA->pRchild = pC;
pB->pLchild = NULL;
pB->pRchild = NULL;
pC->pLchild = pD;
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = NULL;
pE->pRchild = NULL;
return pA;
}
//先序遍历
void PreTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
cout << pT->data << endl;
if (pT->pLchild != NULL)
{
PreTraverseBTree(pT->pLchild);
}
if (pT->pRchild != NULL)
{
PreTraverseBTree(pT->pRchild);
}
}
}
//中序遍历
void InTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
if (pT->pLchild != NULL)
{
InTraverseBTree(pT->pLchild);
}
cout << pT->data << endl;
if (pT->pRchild != NULL)
{
InTraverseBTree(pT->pRchild);
}
}
}
//后序遍历
void PosTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
if (pT->pLchild != NULL)
{
PosTraverseBTree(pT->pLchild);
}
if (pT->pRchild != NULL)
{
PosTraverseBTree(pT->pRchild);
}
cout << pT->data << endl;
}
}
4.1.9树的应用
1.树是数据库中数据组织的一种重要形式
2.操作系统子父进程的关系本身就是一棵树
3.面向对象语言中类的继承关系
4.应用于哈夫曼树
4.2图
6.查找和排序
6.1排序和查找的关系
排序是查找的前提,排序是重点内容。
6.2查找:
6.2.1折半查找
6.3排序:
6.3.1冒泡排序
6.3.2插入排序
6.3.3选择排序
6.3.4快速排序
6.3.5归并排序
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = NULL;
pE->pRchild = NULL;
return pA;
}
//先序遍历
void PreTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
cout << pT->data << endl;
if (pT->pLchild != NULL)
{
PreTraverseBTree(pT->pLchild);
}
if (pT->pRchild != NULL)
{
PreTraverseBTree(pT->pRchild);
}
}
}
//中序遍历
void InTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
if (pT->pLchild != NULL)
{
InTraverseBTree(pT->pLchild);
}
cout << pT->data << endl;
if (pT->pRchild != NULL)
{
InTraverseBTree(pT->pRchild);
}
}
}
//后序遍历
void PosTraverseBTree(pBTNODE pT)
{
if (pT != NULL)
{
if (pT->pLchild != NULL)
{
PosTraverseBTree(pT->pLchild);
}
if (pT->pRchild != NULL)
{
PosTraverseBTree(pT->pRchild);
}
cout << pT->data << endl;
}
}
4.1.9树的应用
1.树是数据库中数据组织的一种重要形式
2.操作系统子父进程的关系本身就是一棵树
3.面向对象语言中类的继承关系
4.应用于哈夫曼树
4.2图
6.查找和排序
6.1排序和查找的关系
排序是查找的前提,排序是重点内容。
6.2查找:
6.2.1折半查找
6.3排序:
6.3.1冒泡排序
6.3.2插入排序
6.3.3选择排序
6.3.4快速排序
6.3.5归并排序