1、理解线性结构、非线性结构、逻辑结构、存储结构的概念、种类、区别
逻辑结构:
简而概之:从具体问题抽象出来的数学模型
概念:从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的
逻辑关系两要素:数据元素;关系
数据元素:数据的基本单位,在计算机中通常作为一个整体进行考虑和处理
关系:关系是指数据元素间的逻辑关系。根据关系之间的不同特性,通常有四类基本结构,复杂程度依次递进:集合结构,线性结构,树结构,图结构或网状结构
线性结构:线表、栈和队列、字符串、数组、广义表
非线性结构:树、二叉树、有向图、无向图
存储结构:
概念:数据对象在计算机中的存储表示,也称为物理结构
顺序存储结构:借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系,通常借助程序设计语言的数组类型来描述
链式存储结构:
顺序存储结构要求所有的元素一次存放在一片连续的存储空间中,而链式存储结构,无需占用一整块存储空间。但为了表示节点间的关系,需要给每个节点附加指针字段,用于存放后续元素的存储地址。所以链式存储结构通常借助于程序设计语言的指针类型来描述
简而言之:数组,占了连续的内存空间,就是顺序存储结构,链表,不连续空间,但是用指针串一串,就是链式存储结构
总结:
逻辑结构是小明是小红朋友,老明是小明他爸这样的,存储结构是你怎么具体把这数据存储起来的,是族谱?是户口本?这样的。族谱在逻辑结构上就是一种非线性结构:树;在存储结构上应该是链式存储结构。户口本在逻辑结构上应该是集合,在存储结构中可以使用顺序结构:数组,字符串组等,也可以使用链式存储结构
2、算法是什么,时间复杂度与常见算法时间复杂度
算法相关概念:
算法(Algorithm):是为了解决某类问题而规定的一个有限长的操作序列
一个算法必须满足以下五个特性
有穷性:算法在有穷步后结束,每步都在有穷时间内完成
确定性:对于每种情况下应该执行的操作,在算法中必须有明确规定,没有二义性
可行性:所有通过可实现的基本操作运算执行有限次可以成功
输入:一个算法有零个或者多个输入
输出:一个算法有一个或者多个输出
评价算法优劣的基本标准:
正确性:在合理数据输入下,能够在有限运行时间内得到正确结果
可读性:一个好的算法,首先应便于人类理解和交流,其次才是机器的可执行性
健壮性:当输入数据非法时,可以做出正确反应进行相应处理,不会出现错误或者莫名其妙的结果
高效性:时间和空间两个方面。时间高效是指算法设计合理,执行效率高,可用时间复杂度度量;空间高效是指算法占用存储容量合理,可以用空间复杂度度量
算法的时间复杂度:
相关知识背景:
衡量算法效率的方法主要有两类:事后统计法和事前分析估算法。
事后统计法就是先实现,然后测算其时间和空间开销。缺点很明显:1、必须把算法转换成可执行的程序、2、时空开销的测算结果依赖于计算机的软硬件等环境因素,容易掩盖算法本身的优劣。所以说通常使用事前分析估算法,通过计算算法的渐近复杂度来衡量算法的效率
问题规模和语句频度:
不考虑计算机的软硬件等环境因素,影响算法时间代价最主要因素是问题规模。
问题规模是算法求解问题输入量的多少,是问题大小的本质表示,一般用整数 n 表示。问题规模 n 对不同的问题含义不同。例如,在排序运算中 n 为参加排序的记录数,在集合运算中 n 为参加排序的记录数等等。显然,n越大算法的执行时间越长。
一个算法的执行时间大致上等于其所有语句执行时间的总和,而语句执行时间则为该条语句的重复执行次数和执行一次所需时间的成绩。
语句频度:一条语句的重复执行次数、
设每条语句执行一次所需的时间均为单位时间,则一个算法的执行时间可用该算法中所有语句频度之和来度量
时间复杂度:
概念:一般将算法的时间复杂度记作:T(n)=O(f(n)),它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度,别管那么多,看就完了
非递归算法的时间复杂度:
常量阶:{x++; s = 0; }
两条语句频度均为1,算法的执行时间是一个与问题规模 n 无关的常数,所以说算法的时间复杂度为T(n) = O(1),称为常量阶
实际上,如果算法的执行时间不随问题规模n的增加而增长,算法中语句频度就是某个常数。即使这个常数再大,算法的时间复杂度都是O(1),如下:
for(int i = 0; i<10000; i++) { x++; s = 0; } 算法的时间复杂度仍为O(1)
线性阶:for( int i = 0; i<n; i++ ) { x++; s=0; }
循环体内两条基本语句的频度均为f(n)=n;所以说算法的时间复杂度为T(n)=O(n),称为线性阶
平方阶:
int x = 0;
int y = 0;
for(int k = 1; k<=n; k++)
x++;
for(int i = 1; i<=n; i++)
for(int j = 1;j<=n;j++)
y++;
对循环语句只需考虑循环体中语句的执行次数,以上程序段中频度最大的语句是第六行,其频度为f(n)=n*n,所以说该算法的时间复杂度为T(n)=O(n*n)称为平方阶。
多数情况下:当有若干个循环语句时,算法的时间复杂度是由最深层循环内的基本语句的频度f(n)决定的
立方阶:平方阶上再套一个有关n的循环
对数阶:for(int i = 1; i<=n; i = i*2) { x++; s = 0; }
设循环体内两条基本语句的频度为f(n),则有2的f(n)次方≤n,f(n)≤ log以2为底n的对数
所以说算法的时间复杂度为T(n)=O(log2n),称为对数阶
总结:
一般情况下,随着 n 的增大,T(n)增长较慢的算法为较优的算法,显然,时间复杂度为指数阶的算法效率极低,当 n 值较大时就无法应用。应该尽可能选择多项式阶(n的k次,k常数)的算法
3、线性表采用顺序存储和链式存储,在内存空间上有什么区别
知识背景:
线性表的定义:
具体例子:26个英文字母的字母表(A,B,....,Z),一个班级里的学生学号表
抽象概念:由 n 个数据特性相同的元素构成的有限序列,n=0就叫空表
对于非空的线性表或线性结构,其特点是:
1 存在唯一的一个被称作“第一个”的数据元素
2 存在唯一的一个被称作“最后一个”的数据元素
3 除去第一个之外,结构中的每个数据元素均只有一个前驱和一个后继
回归主要问题:
线性表的顺序存储表示:
定义:用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表,其特点是:逻辑上相邻的数据元素,其物理次序也是相邻的
解释:第一个数据元素位序为1,存储地址为 b ,则第二个数据元素,即位序为2的元素,其存储地址为 b+1,以此类推
所以说,只要确定了存储线性表的起始位置,线性表中任何一个数据元素都可以随机存储,因为每一个数据元素的存储位置都和线性表的起始位置差一个常数,这个常数和数据元素在线性表中的位序成正比,所以说线性表的顺序存储结构是一种随机存取的存储结构
后话:由于高级程序设计语言中的数组类型也有随机存取的特性,因此,通常都用数组来描述数据结构中的顺序存储结构
线性表的链式存储表示:
特点:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,,也可以是不连续的)。因此,为了表示每个数据元素 ai 与其直接后继数据元素 a(i+1)之间的逻辑关系,对数据元素 ai 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素 ai 的存储映像,称为结点
结点:包含两个域,其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域,指针域中存储的信息称作指针或链。n个节点链接成一个链表,即为线性表(a1,a2...an)的链式存储结构,又由于此链表的每个结点中只包含一个指针域,故又称线性链表或单链表
分类:根据链表结点所含指针个数、指针指向和指针连接方式,可将链表分为:
单链表、循环链表、双向链表、二叉链表、十字链表、邻接表、邻接多重表等。其中单链表、循环链表、双向链表用于实现线性表的链式存储,其它多用于实现图和树等非线性结构
此处只讨论单链表,整个链表的存取必须从头指针开始进行,头指针指示链表中的第一个结点(即第一个数据元素的存储映像,也称为首元结点)的存储位置。同时,由于最后一个数据元素没有直接后继,则单链表中最后一个节点的指针为空(NULL)
总结:
线性表采用顺序存储和链式存储有以下区别:
-
内存分配:顺序存储使用一段连续的内存空间存储线性表的元素,而链式存储使用非连续的存储空间(节点)和指针来连接各个元素。
-
访问效率:顺序存储的线性表可以通过下标直接访问元素,因为元素在内存中连续存储,所以时间复杂度为O(1)。而链式存储的线性表需要通过遍历指针进行元素访问,平均时间复杂度为O(n),其中n是元素个数。
-
空间利用率:顺序存储的线性表存在固定大小的内存空间,若元素个数超过了预分配的空间,则需要重新分配更大的连续内存空间,并将原有元素复制到新的空间中。而链式存储的线性表可以根据需要动态地分配和释放内存空间,不受固定大小的限制。
-
插入和删除操作:顺序存储的线性表在中间位置插入或删除一个元素时,需要移动后续元素的位置,平均时间复杂度为O(n)。而链式存储的线性表在插入或删除一个节点时,只需要改变相邻节点的指针指向,平均时间复杂度为O(1)。
-
随机访问:顺序存储的线性表可以通过下标直接随机访问元素。而链式存储的线性表不支持随机访问,只能依次遍历节点进行访问
存储空间分配:
顺序表的存储空间必须预先分配,元素个数扩充受一定限制,易造成存储空间的浪费或空间溢出现象;而链表不需要为其预先分配空间,只要内存空间允许,链表中的元素个数就没有限制
存储密度大小:
存储密度:数据元素本身所占用的存储量和整个节点结构所占用的存储量之比。存储密度越大,存储空间的利用率就越高。显然,顺序表的存储密度为1,而链表的存储密度小于1.
4、顺序表删除移动元素的个数(删除第一个元素,删除最后一个元素)
和5放一起了
5、线性表的运算特性:定位、插入、删除、合并以及其时间复杂度
定位:(查找?)
概述:查找操作是根据指定的元素值 e,查找顺序表中第1个与 e 相等的元素。若查找成功,则返回该元素在表中的位置序号;若查找失败,则返回0
算法步骤:
1、从第一个元素起,依次和 e 比较,若找到与 e 相等的元素 L.elem[i] ,则查找成功,返回钙元素序号 i+1
2、若查遍整个顺序表都没有找到,则查找失败,返回 0
算法描述
int LocateElem(SqList L, ElemType e)
{
for(int i = 0; i<L.length; i++)
if(L.elem[i]==e)
return i+1;
return 0;
}
【算法分析】
平均查找长度:ASL 在此处中,ASL = (n+1)/ 2,时间复杂度为O(n)
插入:
概述:线性的插入操作是指在表的第 i 个位置插入一个新的数据元素 e ,使长度为 n 的线性表变为长度为 n+1 的线性表。
移动次数:在第 i 个位置插入一个元素时,需从最后一个元素即第 n 个元素开始,以此向后移动一个位置,直到第 i 个元素(共n-i+1个元素)
算法步骤
1、判断插入位置 i 是否合法(i 值的合法范围是 1 ≤ i ≤ n+1),若不合法返回ERROR
2、判断顺序表的存储空间是否已满,若满返回ERROR
3、将第 n 个至第 i 个位置的元素依次向后移动一个位置,空出第 i 个位置(i = n+1时无需移动)
4、将要插入的新元素 e 放入第 i 个位置
5、表长+1
算法描述
Status ListInsert(SqList &L, int i, ElemType e)
{
if( (i<1) || (i>L.length+1) )
return ERROR;
if( L,length == MAXSIZE )
return ERROR;
for(j=L.length-1; j>=i-1; j--)
L.elem[j+1] = L.elem[j];
L.elem[i-1] = e;
++L.length;
return OK;
}
【算法分析】
上述算法没有处理表的动态扩充,因此当表长已经达到预设的最大空间时,则不能再插入元素
插入一个元素所需移动元素期望值:Eins = n/2, 时间复杂度为O(n)
删除:
概述:线性表的删除操作是将表的第 i 个元素删除,将长度为 n 的线性表变成长度为 n-1 的线性表
一般情况下,删除第 i 个元素时需要将第 i+1 个至第 n 个元素 (共 n-i 个元素)一次向前移动一个位置(i=n 时无需移动)
算法步骤
1、判断删除位置 i是否合法,若不合法则返回ERROR
2、将第 i+1 个至第 n 个的元素依次向前移动一个位置
3、表长-1
算法描述
Status ListDelete(SqList &L,int i)
{
if( (i<1) || (i>L.length) )
return ERROR;
for(j = i; j<=L.length-1; j++)
L.elem[j-1] = L.elem[j];
--L.length;
return OK;
}
【算法分析】
删除平均移动次数: Edel = (n-1)/ 2 时间复杂度O(n)
合并:
概述:集合 A与B,要求一个新的集合A = A U B 例如:A = (7,5,3,11)B=(2,6,3)在合并后变成: A =(7,5,3,11,2,6)
可以利用两个线性表LA 和LB 分别表示集合A与B(即线性表中的数据元素为集合中的成员),这样只需要扩大线性表LA,将存在于LB而不存在于LA中的数据元素插入到LA中去,只要从LB中依次取得每个数据元素,并依值在LA中进行查访,若不存在,则插入之
算法步骤
1、分别获取LA表长m和LB表长n
2、从LB中第一个数据元素开始,循环n次执行如下操作
从LB中查找第i个数据元素赋给 e
在LA中查找元素 e,如果不存在,则将 e 插在表LA的最后
算法描述
void MergeList(List &LA, List LB)
{
m = ListLength(LA); n = ListLength(LB);
for(i = 1; i<=n; i++)
{
GetElem(LB,i,e);
if(!LocateElem(LA,e) )
ListInsert(LA,++m,e);
}
}
【算法分析】
上述算法的时间复杂度取决于抽象数据类型List定义中基本操作的执行时间,假设LA和LB的表长分别为 m 和 n,循环执行 n 此,则:
当采用顺序存储结构时,在每次循环中,GetElem 和ListInsert 这两个操作的执行时间和表长无关, LocateElem 的执行时间和表长 m 成正比,因此,算法的时间复杂度为O(m*n)
当采用链式存储结构时,在每次循环中,GetElem的执行时间和表长 n 成正比,而LocateElem和ListInsert这两个操作的执行时间和表长 m 成正比,因此,若假设 m大于n,复杂度同上
6、带头节点的单链表的运算:是否为空、插入(把 s 插入 p 节点后的执行语句),通过一趟遍历确定长度为n的单链表中值最大的节点
是否为空:
带头节点的单链表通过头节点来管理链表的操作,空链表表示链表中没有任何有效节点。因此,我们可以通过判断头节点的指针域是否为空来确定链表是否为空。
具体实现上,可以通过以下步骤进行判断:
-
首先,判断头节点是否存在。如果头节点为空,则认为链表为空。
-
如果头节点不为空,那么通过判断头节点的指针域是否为空来确定链表是否为空。如果头节点的指针域为空,则链表为空;如果头节点的指针域不为空,则链表非空。
需要注意的是,带头节点的单链表在创建时会初始化一个头节点,该头节点不存储任何有效数据,仅用于方便链表的操作和管理。
AI代笔
// 定义单链表节点结构体
struct ListNode {
int val; // 节点的值
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 构造函数
};
// 判断带头节点的单链表是否为空
bool isEmpty(ListNode *head) {
return head->next == NULL;
}
int main() {
// 创建带头节点的空链表
ListNode *head = new ListNode(0);
// 判断链表是否为空
if (isEmpty(head)) {
cout << "链表为空" << endl;
} else {
cout << "链表不为空" << endl;
}
// 释放链表内存
ListNode *p = head;
while (p) {
ListNode *q = p->next;
delete p;
p = q;
}
return 0;
}
查找:
概述:链表中安置查找的过程和顺序表类似,从链表的首元结点出发,一次将节点之和给定值 e 进行比较,返回查找结果
算法步骤
1、用指针 p 指向首元结点
2、用首元结点开始一次顺着链域 next 向下查找,只要指向当前节点的指针 p不为空,并且 p 所指节点的数据域不等于定值 e ,则循环执行以下操作:p 指向下一个节点
3、返回 p。若查找成功,p此时即为节点的地址值,若查找失败,p 的值即为 NULL
算法描述
Lnode *LocateElem(LinkList L, ElemType e)
{
p = L->next;
while( p && p->data!=e)
p = p->next;
return p;
}
【算法分析】
该算法执行时间与待查找的值e相关,时间复杂度也为O(n)
插入(把 s 插入 p 节点后的执行语句)
概述:假设要在单链表的两个数据元素 a 和 b 之间插入一个数据元素 x, 已知 p 为其单链表存储结构中指向结点 a 的指针,为插入数据元素 x,首先要生成一个数据域为 x 的节点,然后插入到单链表中。根据插入操作的逻辑定义,还需要修改结点 a 中的指针域,令其指向结点 x,而结点 x 中的指针域应指向结点 b,从而实现三个元素abx之间逻辑关系的变化,上述指针修改用语句描述即
s->next = p->next; p->next = s;
算法步骤
1、查找节点 a(i-1)并由指针 p 指向该结点
2、生成一个新结点 *s;
3、将新结点 *s的数据域置为 e
4、将新结点 *s 的指针域指向结点a
5、将结点 *p的指针域指向行结点*s
算法描述
Status ListInsert(LinkList &L, int i, ElemType e)
{
p = L; j = 0;
while( p && (j<i-1) )
{
p=p->next;
++j;
}
if( !p || j>i-1)
return ERROR;
s = new LNode;
s->data = e;
s-next = p->next;
p->next = s;
return OK;
}
【算法分析】
单链表的插入操作虽然不需要像顺序表的插入操作那样需要移动元素,但时间复杂度仍为O(n)
这是因为,为了在第 i 个结点之前插入一个新结点,必须首先找到第 i-1 个节点,时间复杂度相同
通过一趟遍历确定长度为n的单链表中值最大的节点
要通过一趟遍历确定长度为n的单链表中值最大的节点,可以使用以下算法:
-
初始化一个指针current指向链表的第一个节点,并将当前节点的值设为最大值max_val,当前节点的位置设为1。
-
从第一个节点开始进行遍历,遍历到最后一个节点。
-
在每个节点上,比较当前节点的值与max_val的大小: a. 如果当前节点的值大于max_val,则更新max_val为当前节点的值,并更新当前节点的位置为该节点的位置。 b. 如果当前节点的值小于等于max_val,则继续遍历下一个节点。
-
遍历结束后,max_val存储的就是链表中值最大的节点的值,而当前节点的位置即为该节点在链表中的位置。
这样就能通过一趟遍历找到长度为n的单链表中值最大的节点。
需要注意的是,在遍历过程中,可以通过移动指针current来依次访问链表中的各个节点,以实现只遍历一次的要求。
AI代笔
// 定义单链表节点结构体
struct ListNode {
int val; // 节点的值
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 构造函数
};
// 查找长度为n的单链表中值最大的节点
ListNode* findMaxNode(ListNode* head, int n) {
if (!head || n <= 0) return NULL; // 处理特殊情况
ListNode *current = head->next; // 初始化指针current指向第一个节点
int max_val = current->val; // 初始化当前节点的值为最大值
int pos = 1; // 初始化当前节点位置为1
ListNode *max_node = current; // 初始化最大节点为第一个节点
// 遍历单链表
while (current) {
if (current->val > max_val) { // 如果当前节点的值大于max_val,则更新max_val和max_node
max_val = current->val;
max_node = current;
}
current = current->next; // 移动指针current到下一个节点
pos++; // 更新当前节点位置
}
// 如果链表长度小于n,则返回NULL
if (pos < n) return NULL;
// 否则返回值最大的节点
return max_node;
}
int main() {
// 创建长度为5的单链表
ListNode *head = new ListNode(0);
ListNode *tail = head;
for (int i = 1; i <= 5; i++) {
ListNode *node = new ListNode(i);
tail->next = node;
tail = node;
}
// 查找长度为5的单链表中值最大的节点
ListNode *max_node = findMaxNode(head, 5);
if (max_node) {
cout << "长度为5的单链表中值最大的节点为:" << max_node->val << endl;
} else {
cout << "链表长度小于5,无法找到值最大的节点" << endl;
}
// 释放链表内存
ListNode *p = head;
while (p) {
ListNode *q = p->next;
delete p;
p = q;
}
return 0;
}
7、理解栈和队列的特点,先进后出、先进先出,在小题中应用,栈和队列的顺序存储和链式存储
知识补充:
栈(stack):限定仅在表尾进行插入或删除操作的线性表。因此,对栈来说,表尾端有特殊含义,称为栈顶(top),相应的,表头端称之为栈底(bottom),不含元素的空表叫做空栈。
栈中元素按a1,a2...顺序进栈,退栈的第一个元素应为栈顶元素,栈的修改是按照后进先出的原则进行的
实例解释:洗干净的盘子总是逐个向上叠放在已经洗过的盘子上面,使用的时候从上往下逐个取用
队列(queue):是一种先进先出的线性表,它只允许在表的一段进行插入,而在另一端删除元素。允许插入的一段称为队尾(rear),允许删除的一段称作队头(font)
实例解释:日常生活中排队,只能在队尾插入,在队头删除
8、串的运算模式匹配、二维数组中采用行优先或者列优先(有时也说 行序为主序或列序为主序),计算a [i] [j] 的地址
9、广义表的长度、深度、GetTail ()方法的功能
10、二叉树的基本形态、二叉树的基本概念(双亲、孩子、子孙、树的高度、根节点、叶子节点等),完全二叉树的特点
知识补充:
树的基本术语:
结点:树中的一个独立单元。包含一个数据元素及若干指向该子树的分支
结点的度:结点拥有的子树数称为结点的度
树的度:树的度是树内各结点度的最大值
叶子:度为0的结点称为叶子或者终端结点
非终端结点:度不为0的结点称为非终端结点或者分枝结点。除根节点之外,非终端结点也称为内部结点
双亲和孩子:结点的子树的根称为该结点的孩子,相应的,该结点称为孩子的双亲
兄弟:同一个双亲的孩子之间互称兄弟
祖先:从根到该结点所经分支上的所有结点
子孙:以某结点为根的子树中的任一结点都成为该结点的子孙
层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。树中任一结点的层次等于其双亲结点的层次+1
堂兄弟:双亲在同一层的节点互为堂兄弟
树的深度:树中结点的最大层次称为树的深度或高度
有序树和无序树:如果将树中结点的各子树看成从左到右是有次序的(不能互换),则称其为有序树,否则为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子
森林:是m(m>=0)棵不相交的树的集合。对于树中每个结点而言,其子树的集合即为森林。由此也,可以用森林和树相互递归的定义来描述树
就逻辑结构而言,任何一棵树都是一个二元组 Tree = ( root, F ),其中 root 是数据元素,称作树的根节点;F 是 m 棵树的森林, F = (T1,T2,...Tm),其中Ti = ( ri, Fi ) 称作根root 的第 i 棵子树;当 m ≠ 0 时,在树根和其子树森林之间存在下列关系
RF = { < root, ri > | i = 1, 2, 4, m, m>0 }
这个定义将有助于得到森林和树与二叉树之间转换的递归定义
二叉树的定义:
二叉树(Binary Tree)是 n(n≥0)个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:
1、有且仅有一个称之为根的结点
2、除根节点以外的其余结点分为两个互不相交的子集 T1 和 T2,分别称为 T 的左子树和右子树,且 T1 和 T2 本身都是二叉树
二叉树与树一样具有递归性质,二叉树与树的区别主要有以下两点:
1、二叉树每个结点至多只有两棵子树(二叉树中不存在度大于2的节点)
2、二叉树的子树有左右之分,其次序不能任意颠倒
二叉树的性质:
性质1 在二叉树的第 i 层上至多有 个结点 (i≥1)
性质2 深度为 k 的二叉树至多有 个结点 (k≥1)
性质3 对于任何一棵二叉树 T,如果其终端结点数为 ,度为 2 的结点数为
则
特殊二叉树:满二叉树、完全二叉树
满二叉树:深度为 k 且含有 个节点的二叉树
满二叉树的特点:每一层上的结点数都是最大结点数,即每一层 i 的节点数都具有最大值
可以对满二叉树的结点进行连续编号,约定编号从根节点起,自上而下,自左至右,由此可以引出完全二叉树的定义
完全二叉树:深度为k 的,有 n 个节点的二叉树。当且仅当每一个结点都与深度为 k 的满二叉树中编号从 1 到 n 的结点一一对应时,称之为完全二叉树
完全二叉树的特点:
1、叶子节点孩子可能在层次最大的两层上出现
2、对任一结点,若其右分支下的子孙的最大层次为 l,则其左分支下的子孙的最大层次必为 l 或 l+1
11、二叉树的三种遍历方法、二叉树的遍历算法实现、计算二叉树的叶子节点算法
遍历二叉树(traversing binary tree)是指按照某条搜索路径巡访树中每个结点,使得每个结点均被访问一次,且仅被访问一次。
回顾二叉树的递归定义可知,二叉树是由3个基本单元组成:根节点、左子树和右子树。因此,若能依次遍历这三部分,便是遍历了整个二叉树
先序遍历二叉树的操作定义如下:
若二叉树为空,则空操作;否则:
1、访问根节点;2、先序遍历左子树;3、先序遍历右子树
中序遍历二叉树的操作定义如下:
若二叉树为空,则空操作;否则:
1、中序遍历左子树;2、访问根节点;3、中序遍历右子树
后续遍历二叉树的操作定义如下:
1、后序遍历左子树;2、后序遍历右子树;3、访问根节点
可知:3种遍历算法不同处仅在于访问根节点和遍历左右子树的先后关系。如果在算法中暂且抹去和递归无关的cout语句,则3个遍历算法完全相同
12、根据两种遍历序列确定一颗二叉树
bilibili视频挺多的,自己查吧
13、哈夫曼树的构造、带权路径长度、哈夫曼编码
哈夫曼树的基本概念
哈夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,先补充知识:
1、路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径
2、路径长度:路径上的分支数目称作路径长度
3、树的路径长度:从树根到每一结点的路径长度之和
4、权:赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有结点(元素)和边(关系)两大类,所以对应有结点权和边权,则对应的就有带权树等概念
5、结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的成绩
6、树的带权路径长度:树中所有叶子节点的带权路径长度之和,记作WPL =
7、哈夫曼树:假设有 m 个权值{w1,w2,...wm},可以构造一棵含 n 个叶子节点的二叉树,每个叶子结点的权为 wi,则其中带权路径长度 WPL 最小的二叉树称作最优二叉树或哈夫曼树
哈夫曼树中,权值越大的节点离根节点越近,由这个特点,引出构造哈夫曼树方法——哈夫曼算法
哈夫曼树的构造bilibili上视频也很多,自己查
14、前缀编码
概念:如果在一个编码方案中,任一个编码都不是其它编码的前缀,则称编码是前缀编码。前缀编码可以保证对压缩文件进行解码时不产生二义性,确保正确编码
15、图的基本概念(分类、度)、图的遍历(深度优先、广度优先)
16、给定一个无向网,能写出其邻接矩阵、邻接表,基于此邻接矩阵或邻接表从某个起点触发给出深度优先和广度优先遍历序列
17、给定一个有向图,能写出其邻接矩阵、邻接表,基于此邻接矩阵或邻接表从某个起点触发给出深度优先和广度优先遍历序列
18、已知一个有序表,给出其对应的折半查找判定树,并能计算查找成功时的ASL
19、顺序表的查找(加入哨兵)、查找成功、查找失败比较的情况
20、折半查找的查找方法、算法实现
21、分块查找
22、已知一个二叉排序序列,画出二叉排序树、并求平均查找长度
23、能写出在 带头节点的单链表中 某个几点的前面或后面 插入一个节点 的语句序列。注意:“在首元结点前插入”指的是头结点后面、首元结点前面。“在尾元结点后插入”指的是链表最后一个节点后面。提示:可能需要遍历链表找到插入位置
24、循环队列中常用的队满判断条件是少用一个元素空间、入队、出队
25、已知待排序关键字序列,给出直接插入排序、希尔排序和快速排序、简单选择排序、2路归并排序的思想及排序过程
未完待续。。。。。。
考完试了,没意外是不再更新,虽然后面有的只有标题,但也可以作为提纲进行指导复习