数据结构初阶:二叉树_struct treenode,2024年最新成功跳槽阿里

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注网络安全)
img

正文

上面的方式各有优劣,表示树结构的最优方法是左孩子右兄弟表示法

struct TreeNode 
{
    TNDataType data; 

    struct TreeNode\* firstChild;
    struct TreeNode\* nextBrother;
};

结点的指针域只存两个指针:

  • firstChild指向该结点的第一个子结点,
  • nextBrother指向子结点右边的第一个兄弟结点。以此像单链表的形式链接兄弟节点。

第一层,根结点

A

A

A ,无兄弟结点。

第二层,结点

A

A

A 的第一个子结点为

B

B

B,其兄弟结点为

C

C

C。

第三层,结点

B

B

B 的第一个子结点为

D

D

D,其兄弟结点为

E

E

E,

F

F

F。结点

C

C

C 的子结点为

G

G

G。

第四层,结点

D

D

D 无子结点,结点

E

E

E 有子结点为

H

H

H。结点

F

F

F,

G

G

G 无子结点 … ….

只要确定根结点,其余所有的结点都可以从其父结点或兄弟结点的指针处找到,如果没有指针就为空。

这种方法不需要确定树的度

N

N

N,也不需要使用线性表存储,结构不复杂也不浪费空间,不失为树结构的最优表示法。

树在计算机中最经典的应用就是文件管理系统即目录树。当打开文件夹时,弹出的一系列子文件夹,更类似于先找到子结点再找到其兄弟结点。

2. 二叉树

用于存储和管理数据,最常见的是二叉树。

2.1 二叉树的定义

二叉树的每个结点可无子树或有一个子树,但最多有两个子树,分别称为左子树和右子树。如图:

  • 二叉树不存在度大于2的结点。同样二叉树的度最大为2。

度为0时树为空。度为1时是线性结构,度为2时存在可能有两个子树的结点。

  • 二叉树中结点的子树有左右之分,且次序不可颠倒。
  • 任意二叉树由以下几种情况复合而成:

特殊二叉树

二叉树中有两种特殊的二叉树,分别是满二叉树和完全二叉树。

满二叉树:所有的叶结点都在最后一层,也就是所有分支结点都有两个子树,也就是每层结点数都到达最大值,这样的数就是满二叉树。

假设满二叉树的层数

h

h

h,则第

h

h

h 层的结点数为

2

h

1

2^{h-1}

2h−1,结点总数是

2

h

1

2^h-1

2h−1 。

若已知结点总数为

N

N

N, 则树的高度为

l

o

g

2

(

N

1

)

log_2(N+1)

log2​(N+1)。

完全二叉树:完全二叉树的前

n

1

n-1

n−1 层为满二叉树,最后一层虽可不满但一定是从左至右连续。

满二叉树是一种特殊的完全二叉树。

二叉树的性质
  1. 二叉树的第

i

i

i 层上最多有

2

i

1

2^{i-1}

2i−1 个结点。
2. 对于深度为

h

h

h 的二叉树,最大结点数为

2

h

1

2^h-1

2h−1,最少节点数为

2

h

1

2^{h-1}

2h−1。
3. 任意二叉树,假设其叶结点个数

n

0

n0

n0 总比 度为2的分支结点

n

2

n2

n2 个数大

1

1

1,即

n

0

=

n

2

1

n_0=n_2+1

n0​=n2​+1。

二叉树的特点就是:每增加一个分支结点,必然会增加一个叶节点。

  1. 完全二叉树度为

1

1

1 的结点个数,要么为

0

0

0,要么为

1

1

1。
5. 若满二叉树结点总数为

N

N

N, 则树的高度为

h

=

l

o

g

2

(

N

1

)

h=log_2(N+1)

h=log2​(N+1)。

2.2 二叉树的结构

普通二叉树的增删查改无甚意义,更多是学习对二叉树结构的控制。为后期学习搜索二叉树、AVL树和红黑树夯实基础。

顺序存储

顺序存储即用数组按层序顺序一层一层的存储节点。

有些“缺枝少叶”的树存入数组,若不浪费空间便不好规律地表示结构。故一般数组只适用于表示完全二叉树

更重要的是,可以利用数组下标计算结点的父子结点位置。如图:

l

e

f

t

C

h

i

l

d

=

p

a

r

e

n

t

2

1

r

i

g

h

t

C

h

i

l

d

=

p

a

r

e

n

t

2

2

leftChild=parent*2+1\ rightChild=parent*2+2

leftChild=parent∗2+1rightChild=parent∗2+2

如果计算得的孩子下标越界,则说明该节点不存在对应的子节点。

p

a

r

e

n

t

=

(

c

h

i

l

d

1

)

/

2

parent=(child-1);/;2

parent=(child−1)/2

链式存储

使用链表表示二叉树,更加的直观。通常方案有两种一个是二叉链表,一个是三叉链表。二叉链表即存数据域和左右指针域,三叉则多存一个父结点指针。

当前数据结构一般都是二叉链,红黑树等高阶数据结构会用到三叉链。当前仅作了解。

// 二叉链
struct BinaryTreeNode {
    struct BinTreeNode\* leftChild;
    struct BinTreeNode\* rightChild;
	BTDataType _data; 
};
// 三叉链
struct BinaryTreeNode {
	struct BinTreeNode\* parentChild;
	struct BinTreeNode\* leftChild;
	struct BinTreeNode\* _pRight; 
	BTDataType _data;
};

3. 堆

3.1 堆的定义

堆是一种数据结构,他是完全二叉树的一种应用,故堆的底层采用数组作底层结构。

需注意,此刻所讨论的堆是一种抽象数据结构,和内存中的堆没有关系。

定义一个值的集合

{

k

0

,

k

1

,

k

2

,

.

.

.

,

k

n

1

}

\lbrace k_0,k_1,k_2,…,k_{n-1} \rbrace

{k0​,k1​,k2​,…,kn−1​},将其以二叉树顺序存储(层序遍历)的方式存储于数组中,且满足一定规律:

K

i

K

2

i

1

&

&

K

i

K

2

i

2

K_i ≤ K_{2*i+1}; && ; K_i ≤ K_{2*i+2}

Ki​≤K2∗i+1​&&Ki​≤K2∗i+2​

K

i

K

2

i

1

&

&

K

i

K

2

i

2

K_i ≥ K_{2*i+1}; && ; K_i ≥ K_{2*i+2}

Ki​≥K2∗i+1​&&Ki​≥K2∗i+2​

  • 公式

(

5

)

(5)

(5) 要求每个结点都比其子结点小或相等,这样的堆被称为小堆或小根堆

  • 反之,公式

(

6

)

(6)

(6) 要求每个结点都比其子结点大或相等,这样的堆被称为大堆或大根堆

可以看出,堆是一个完全二叉树,且堆中某个结点的值总是不大于或不小于其子结点的值。但堆并不是有序的,只有存储堆的数组有序,才称堆有序。

3.2 堆的实现

堆的逻辑结构是一个完全二叉树,物理结构是一个数组。也可以认为完全二叉树实际上就是个数组,或着是把数组想象成完全二叉树。

堆的结构
typedef int HPDataType;

typedef struct heap {
    HPDataType\* a;
	int size;
    int capacity;
}heap;

堆的插入
void HeapPush(heap\* php, HPDataType x) 
{
	assert(php);
	if (php->size == php->capacity) 
    {
		int newCapacity = php->capacity == 0 ? 4 : php->capacity \* 2;
		HPDataType\* tmp = (HPDataType\*)realloc(php->a, sizeof(HPDataType) \* newCapacity);
		if (tmp == NULL) 
        {
			perror("HeapPush::malloc");
			exit(-1);
		}
		
        php->a = tmp;
		php->capacity = newCapacity;
	
    }
	php->a[php->size] = x;
	php->size++;
	
    //调整
	AdjustUp(php->a, php->size, php->size - 1);
}

堆插入就是在数组的末尾进行插入,就是在二叉树上加一个叶结点。

由于插入的数值不一定,堆的性质可能被破坏。但插入新结点只会影响其到根结点的这条路径上的结点,故需要顺势向上调整:一直交换结点数值直到满足堆的性质即可。

向上调整算法
void AdjustUp(HPDataType\* a, int size, int child) 
{
	assert(a);
    int parent = (child - 1) / 2;
    
	while (child > 0) 
    {
		//大堆
		if (a[child] > a[parent]) 
			Swap(&a[child], &a[parent]);
		else 
			break;
        
		//迭代
        child = parent;
        parent = (child - 1) / 2;
	}
}

向上调整算法,从child处一直向上找父结点,满足子结点比父节点大或小的条件就交换,直到调整到根结点或不满足条件为止。

堆的向上调整较为容易,因为结点的父结点只有一个,只需要和父节点比较即可。

堆的删除
void HeapPop(heap\* php) 
{
	assert(php);
	assert(!HeapEmpty(php));

    //删除
	php->a[0] = &php->a[php->size - 1];
	php->size--;
	
    //调整
	AdjustDown(php->a, php->size, 0);
}

堆的删除就是删除堆顶元素,但不能简单的将数组整体向前挪一位,这样会使破坏堆的结构。

应该先修改堆顶元素的值为数组末尾元素的值,再删除数组末尾元素。此时再从堆顶位置向下调整,就能恢复堆结构。

向下调整算法
//大根堆
void AdjustDown(HPDataType\* a, int size, int parent) 
{
	int child = parent \* 2 + 1;

	while (child < size) // 等遍历到叶节点时,child迭代到叶节点的子节点必越界
    {
		if (child + 1 < size && a[child + 1] > a[child]) // 选出大子结点
			child++;
		
		//交换
		if (a[child] > a[parent]) 
			Swap(&a[child], &a[parent]);
		else 
			break;
        
        //迭代
        parent = child;
        child = parent \* 2 + 1;
	}
}

把尾元素换到堆顶,必然会改变堆的性质。但根结点的左右子树还是保持原有的性质。所以只需要将堆顶元素逐步向下调整。

以大根堆为例,从根开始,将当前结点与其较大的子结点进行交换,直到走到叶结点或不满足条件为止。

将较大的子结点换上来就是在恢复大堆性质,将较小的子结点交换上来是在恢复小堆性质。

堆的插入删除的时间复杂度,也就是向上向下调整算法的时间复杂度都是

l

o

g

N

logN

logN。

建堆

给出数组a,数组逻辑上可以看成完全二叉树,但并不一定是堆。建堆就是将数组调整成堆。

方法1:向上调整

从根结点开始,依次将数组元素“插入”堆,与其说是“插入”不如说是“加入”。利用下标遍历数组,每插入一个就调整一次。

假设需要将a排成升序,不妨先试试将a数组构建成小堆:

//建堆
void HeapBuild(int\* a, int sz) {
    //向上调整
	for (int i = 1; i < sz; i++) {//从第二个结点开始遍历到尾结点
		AdjustUp(a, sz, i);
	}
}

每加入一个元素,就向上调整。思想上其实和接口Push是一样的,都是插入再调整。也可以理解为“边建边调”。

方法2:向下调整

此时数组当然还不是堆,向下调整算法要求左右子树必须满足堆的性质,才能将当前节点向下调整。应先从最后一个子树开始向下调整,从后向前倒着遍历。

准确来说,因为叶结点必然满足堆的性质,所以不用关心。应从尾结点的父结点所在子树开始,遍历到根结点进行调整。

//建堆
void HeapBuild(int\* a, int sz) {
    //向下调整
	for (int i = (sz - 1 - 1) / 2; i >= 0; i--) {//从最后一个叶结点的父结点开始到根结点
		AdjustDown(a, sz, i);
	}
}	

从一个完全二叉树的尾结点的父结点开始,从后往前调,也可以看成“建完在调”。

建堆的两种方式,向上调整和向下调整都是可行的。建大堆还是建小堆,只要改比较符号即可。

建堆复杂度

遍历数组 N 个节点,每个节点调整 logN 次,故向上调整建堆的时间复杂度为

O

(

N

l

o

g

N

)

O(N*logN)

O(N∗logN) 。

向上调整算法复杂度过高,建堆一般配合堆排序使用的是向下调整算法。

向下调整的最复杂情况是从根结点一直调整到叶结点,并以满二叉树为例,看最复杂情况。

假设当前树有

n

n

n 个结点,树的高度为

h

h

h ,可得:

  1. 第 1 层有

2

0

2^0

20 个结点,每个结点最多调整

h

1

h-1

h−1 次,
2. 第 2 层有

2

1

2^1

21 个结点,每个结点最多调整

h

2

h-2

h−2 次,
3. 以此类推,第

h

1

h-1

h−1 层有

2

h

2

2^{h-2}

2h−2 个结点,每个结点最多调整

1

1

1 次。

精确计算下,第

x

x

x 层的所有节点的总调整次数,应为

2

x

1

(

h

x

)

2^{x-1}*(h-x)

2x−1∗(h−x)。

T

(

n

)

T(n)

T(n) 为差比数列,利用错位相减法得

T

(

n

)

T(n)

T(n) 关于

h

h

h 的表达式,再由

n

=

2

h

1

,

h

=

l

o

g

2

(

n

1

)

n=2^h-1,h=log_2{(n+1)}

n=2h−1,h=log2​(n+1) 将

T

(

n

)

T(n)

T(n)转换成关于的

n

n

n的表达式。

由此可得,**向下调整建堆的时间复杂度为

O

(

N

)

O(N)

O(N)。**

3.4 堆的应用
堆排序

堆排序,即利用堆的实现思想对现有的数组进行排序。

假设数组a={70,56,30,25,15,10,75},我们需要先将数组建成堆,然后才能再进行堆排序。

建堆分析

前面已经介绍过堆的创建的两种方式,调用建堆函数即可。

假设要将a排成升序,构建成大堆还是小堆呢?

如果建小堆,堆顶元素即最小的数。若想选出次小的数,就要从第二个位置开始重新建堆,也就是破坏堆的结构重新建堆。

不允许开新空间,那只能重新建堆。重新建堆的复杂度为

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

o

g

2

(

n

1

)

n=2^h-1,h=log_2{(n+1)}

n=2h−1,h=log2​(n+1) 将

T

(

n

)

T(n)

T(n)转换成关于的

n

n

n的表达式。

由此可得,**向下调整建堆的时间复杂度为

O

(

N

)

O(N)

O(N)。**

3.4 堆的应用
堆排序

堆排序,即利用堆的实现思想对现有的数组进行排序。

假设数组a={70,56,30,25,15,10,75},我们需要先将数组建成堆,然后才能再进行堆排序。

建堆分析

前面已经介绍过堆的创建的两种方式,调用建堆函数即可。

假设要将a排成升序,构建成大堆还是小堆呢?

如果建小堆,堆顶元素即最小的数。若想选出次小的数,就要从第二个位置开始重新建堆,也就是破坏堆的结构重新建堆。

不允许开新空间,那只能重新建堆。重新建堆的复杂度为

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
[外链图片转存中…(img-UC3B9PRX-1713610433748)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值