数据结构算法学习复习整理

总览思维导图

排序算法

排序当中比较重要的部分:
排序的过程
排序中特别重要的流程。

比较难且重要的递归排序

归并排序——Merge——mid

代码

void process(int []arr,int L,int R)
{
	if(L==R)
	{
		return;
	}
	int mid = L + ((R-L)>>1);
	process(arr,L,mid);
	process(arr,mid+1,R);
	merge(int []arr,int L,int M,int R);


}

递归思想
基递归

是当一部分L和R相等的时候或者L>R的时候
这样我们需要的结果就是,当有以上两种情况的时候将返回。停止继续向下递归。

关于归并当中的Merge排序代码

代码特征

merge的特征3个while,还需要额外的动态分配空间的一个辅助数组。

心得

最主要的就是关于各种临届条件一定要对,其实写的越多就法线越简单。
主要的这种临届条件主要分为两个大方向,这个临届变量主要就是针对于排序方法的右边界内容。
一方面可以使这个右边界是数组长度,在访问下标的时候就不能访问到这个右边界
因为是递归调用
右边界的设定不一样所影响的各种边界因素也不一样

  • base case的判断条件

如果临届条件的设定是不包含右边界 ,那么永远不会有L R相等的情况。return的情况就是L比R小一个的时候。这个时候范围内只有一个元素,本身有序无序排序直接返回。
相反如果R包含右边界,那么只有当L==R的时候这个时候范围内只有一个元素可以直接返回。
关于mid,因为mergeSort和merge两个函数都会用到,所以可以只计算一遍节省空间。
下面就是关于左右两部分的指针,假设这两个指针的变量名分别为p1和p2。那么当R不被包含的时候在任何情况下P1=L,P2
如果R被包含

void mergeSort(int arr[],int L,int R)
{
			//当R不被包含
			if((R-L)==1)
			{
					return;
			}
			mergeSort(arr,L,mid);
			mergeSort(arr,mid,R);
			//当R被包含的时候
			if(R==L)
			{
				mergeSort(arr,L,mid);
				mergeSort(arr,mid+1,R);
				
			}

}
关于merge函数
void merge(int arr[],int L,int R,int mid)
{
		//当不包含R的时候
		int *help=new int[L-R];
		int p1 =L;
		int p2=mid;
         while(p1<mid&&p2<R)

		//当包含R的时候
		int *help = new int[R-L+1]
		int p1=L;
		int  p2=mid+1;
		while(p1<=mid,p2<=R)

}

那么base case 就是

** more important ** 
关于求mid中值,>>这个运算符的优先级别特别的高。
正确的写法为:int mid = L + ((R-L)>>1);
void mergeSort(int arr[],int L,int R)
{
	if(L<R)
	
}

iteratemethond

void iteratemerge(int arr[],int length)
{
	int steplength = 1;
	int L = 0;
	int r = L + steplength;
	int sL = 0;
	int sr = 0;
	//步长循环
	while (2*steplength<length-1)
	{
		//单个步长内部的循环
		while (r < length)
		{
			int i = 0;
			int* help = new int[2*steplength];
			sL = L;
			sr = r;
			while (L<sr&&r<sr+steplength)
			{
				help[i++] = arr[L] > arr[r] ? arr[r++] : arr[L++];
			}
			while (L<sr)
			{
				help[i++] = arr[L++];
			}
			while (r < sr+ steplength)
			{
				help[i++] = arr[r++];
			}
			for (int i = 0; i <2 * steplength; i++)
			{
				arr[sL + i] = help[i];
			}
			delete[] help;
			//bug多的原因就是 前后的L 和 r 不一样

			//更新L 和 r的值
			
			L = r;
			r = r + steplength;
		}
		steplength = steplength * 2;
		L = 0;
		r = steplength;

		/*if (r + steplength < length)
			{
				
				steplength = steplength * 2;
				L = 0;
				r = steplength;

			}
			else
			{
				steplength = steplength * 2;

				L = 0;
				r = steplength;
			}*/
	}
	L = 0;
	r = steplength;
	/*这个if用来处理的情况是可能数组的长度是奇数,或者数组的长度有几个多余出来了*/
	if (steplength < length)
	{
		int* help = new int[length];
		int i = 0;
		while (L < length && r < length)
		{
			help[i++] = arr[L] > arr[r] ? arr[r++] : arr[L++];
		}
		while (L < length)
		{
			help[i++] = arr[L++];
		}
		while (r < length)
		{
			help[i++] = arr[r++];
		}

		for (int i = 0; i < length; i++)
		{
			arr[i] = help[i];
		}
		delete[] help; //清空自己申请的空间
	}
}
用到额外数据变量

归并排序的原理主要是依靠merge来完成的。
首先根据传进来的数组的值以及L,和R两个数。我们可以确定出mid中点部分。
然后根据这个中点将数据分为左右两个部分。
左部分:L 到M
右部分:M+1 到 R
申请两个指针:一个指针指向左边部分的第一个数据。另一个指针指向右边部分的第一个数据。
然后一个while结构:

help[i++] = arr[p1] <= arr[p2] ? arr[p1++]:arr[p2++]
/*
其中的help是用到的额外的数组。这个额外数组用到的还是动态数组相关的内容
关于动态数组待补充:
我们需要的这个数组的大小是在运行过程当中决定的,而不是在编译过程中决定的。



*/

关于动态数组
动态数组的空间的申请
int *help =new int[length];
变量length可以在运行的时候决定数组help的长度
当数组不用的时候需要进行手动销毁
delete [] help;

在两个指针在第一个while里面逐渐向后走的时候,会发生三种情况。
第一种情况:两个指针同时走完,此时左右部分的数据内容是一个大,另一个小。
第二种情况:不管是左指针还是右指针,总有一个指针是提前走完的。
下面的两个while就是处理这种情况的:
第一个while处理右指针首先走完的情况,将剩余的左部分内容自动放到help里面去
第二个while和第一个相反,将右边剩余的内容放到help当中去。
此时一部分的内容就已经完成排序过程。
然后将help当中排序好的内容copy到原来的数组当中对应的位置上去。
将数组序列通过递归不断的依靠递归平分下去。然后通过排序来完成这个过程。

归并排序相关的经典题目(都需要对merge函数进行不同程度的改写)
小和问题

原题目:给你一个数组,然后从数组第一个数都最后一个找这个数左边比这个数小的数的所有数的和。

暴力方法:
所有问题的暴力方法基本都可以通过两个for循环来进行执行

int smallsum1(int arr[], int length)
{
	int sum = 0; // the sum of all small sum
	for (int i = 0; i < length; i++)
	{
		if (i > 0)
		{
			for (int j = i-1; j >=0; j--)
			{
				if (arr[i] > arr[j]) { sum+=arr[j]; }
			}
		}
	}
	return sum;
}

归并排序的方法:

思路:

首先需要将问题进行转化:

我们可以将求左边比自己小的数转化为------ > 求右边比我大的数字
由于归并排序的时候左半部分和右半部分都是有序的,目的就是找右边比自己大的有多少个
一旦找一个的话,说明后面的都是符合条件的
知道当前符合要求的数字后,求后面一共有多少个
当R作为最后一个数字的小标的时候一共是 R - pr +1
当R作为最后一个元素下标后面的数的时候 R- pr == 6

之后我们可以用归并排序进行求,归并排序求这个问题所需要的改动是比较小的
归并排序代码最主要 的特征就是使用3个while来完成merge的过程

此时的R是数组最后一个元素的下标 9
int smallsum2(int arr[], int L,int R)
{
	if (L == R||R-L==1) { return 0; }

	int mid = L + ((R - L) >> 1);

	return smallsum2(arr, L, mid)   //将左边求的小和和右边的小和最终一起相加。
		+
		smallsum2(arr, mid, R)
		+
		smallsummerge(arr, L, R,mid);
}
/*至关重要的merge函数*/
int smallsummerge(int arr[],int L,int R,int mid)
{
	int length = R - L;
	int* help = new int[length];

	int pl=L; //左侧的指针
	int pr = mid; //右侧的指针
	int res = 0;
	int i=0; // 向help当中添加元素的指针。

	//当两个一样的时候一定要右边的内容先下去
	while (pl < mid && pr < R)
	{
	//merge将一组数分为左右两部分,当左边的数小于右边的时候,左边的数字乘以右边的数字的个数
	/* arr[pl] 就是左边指针指的数字
		R-pr 表示右边的边界减去
	*/
		res += arr[pl] < arr[pr] ? arr[pl]*(R-pr) : 0;    //小和改写的重要内容
		help[i++] = arr[pl] < arr[pr] ? arr[pl++] : arr[pr++];
	}
	while (pl < mid)
	{
		help[i++] = arr[pl++];
	}

	while (pr < R)
	{
		help[i++] = arr[pr++];
	}

	for ( int i = 0; i <R-L; i++)
	{
		arr[L + i] = help[i];
	}
	delete [] help; //释放使用的临时数组help的空间
	return res;
}
代码特征

用到的变量
merge左右的两个指针,pl和pr
一个辅助空间数组 int *help =new int[];
变量i

最主要的特征就是3个while
以及最后一个for用来将help临时数组内的东西转移到原数组arr当中去。
自己动态申请的内存空间都在堆里面
最后一定要记得释放help临时数组所占用的空间

逆序对问题

原题目:给定一个数组,这个数右边找到一个数比这个数小的数。这两个数可以被称为一个逆序对。

暴力方法
int reversepair1(int arr[], int length)
{

	int sum = 0;
	for (int i = 0; i < length; i++)
	{
		for (int j = i+1; j < length; j++)
		{
			if (arr[i] > arr[j]) { sum++; } //如果前面的i大于后面的j那么我们就认为数逆序对
		}
	}

	return sum;
}
归并改写的方法
此时的R是数组最后一个元素的下一个,是数组当中元素的总个数
int reversepairmerge2(int arr[], int L,int R)
{
	if (L == R) { return 0; }
	int mid = L + ((R - L) >> 1);

	return
	reversepairmerge2(arr,L, mid)
		+
	reversepairmerge2(arr,mid+1, R)
		+
	processmerge(arr, L, R);
}

int processmerge(int arr[], int L, int R)
{
	if (L == R) { return 0; }
	int mid = L + ((R - L) >> 1);

	int pl = mid;
	int pr = R;
	int* help = new int[(R - L + 1)];
	int res = 0;
	int i = R-L;
	while (pl>=L&&pr>mid)
	{
		arr[pl] > arr[pr] ? res+=(pr-mid) : 0;
		help[i--] = arr[pl] > arr[pr] ? arr[pl--] : arr[pr--];
	}
	while (pl >= L)
	{
		help[i--] = arr[pl--];
	}
	while (pr > mid)
	{
		help[(i--)] = arr[pr--];
	}
	for (int i = R - L ; i >= 0; i--)
	{
		arr[L + i] = help[i];

	}
	return res;
}
左边比右边大两倍的问题

在这里插入图片描述

暴力方法
int BiggerThanRighterTwice1(int arr[], int length)
{
	int res = 0;
	for (int i = 0; i < length; i++)
	{
		for (int j = i + 1; j < length; j++)
		{
			if (arr[i] > arr[j] * 2)
			{
				res++;
			}
		}
	}
	return res;
	cout << res;
}
采用归并的方法
//关于大两倍的题目,同样是通过
int BiggerThanRighterTwice(int arr[], int length)
{
	int stepsize = 1;
	int ans = 0/*这个问题需要了解套了多少个循环 
	* 变量 四个变量:L和r
	* sL 和 sr
	* 
	第一个循环 stepsize
	第二个循环 在这个stepsize下先前走更新L 和更新r
	*/
		int L = 0;
		int r = L + stepsize;
	while (2*stepsize < length)
	{
		int* help = new int[2 * stepsize];
		int sL = L;
		int sr = r;
		int i = 0;
		while (r<length)
		{
			while (L<sr&&r<sr+stepsize)
			{
				arr[L] > arr[r] * 2 ? ans++ : 0;   //关键的改写语句
				help[i++] = arr[L] > arr[r] ? arr[r++] : arr[L++];
			}
			while (L<sr)
			{
				arr[L] > arr[r] * 2 ? ans++ : 0;   //关键的改写语句
				help[i++] = arr[L++];
			}
			while (r<sr+stepsize)
			{
				arr[L] > arr[r] * 2 ? ans++ : 0;   //关键的改写语句
				help[i++] = arr[r++];
			}

			for (int i = 0; i < 2 * stepsize; i++)
			{
				arr[sL + i] = help[i];
			}
			i = 0;
			L = r;
			r = r + stepsize;
			sr = r;
			sL = L;

		}
		stepsize = stepsize * 2;
		L = 0;
		r = L + stepsize;
	}

	if (stepsize < length)
	{
		L = 0;
		int* help = new int[length];
		r = L + stepsize;
		int sr = r;
		int sL = L;
		int i = 0;
		while (L<sr&&r<length)
		{
			arr[L] > arr[r] * 2 ?ans++:0;
			help[i++] = arr[r] < arr[L] ? arr[r++] : arr[L++];

		}
		while (L<sr)
		{
			arr[L] > arr[r-1] * 2 ? ans++ : 0;
			help[i++] = arr[L++];
		}
		while (r<length)
		{
			arr[L] > arr[r-1] * 2 ? ans++ : 0;

			help[i++] = arr[r++];
		}
		for (int i = 0; i < length; i++)
		{
			arr[i] = help[i];
		}
	}
	return ans;
}
固定范围求子数组个数的问题

这个问题是最难的,
在这里插入图片描述

关于该问题的解题思路

需要将问题进行很大程度上比较复杂的简化,将复杂的问题处理为比较简单的问题

  1. 最原始的问题是:求数组当中数组的累加和在指定的范围内的个数
  2. 暴力的方法是将数组的所有子数组的累加和求解出来,然后判断有多少个符合条件
  3. 假设我们有一个10个元素的数组,我们想要求4到8的累加和,这个问题的结果等同于0到8的累加和减去0到4的累加和的结果就是4到8的累加和。这样可以大大加快求解指定范围的累加和的速度。前提是我们要将数组的前缀和全部求解出来

4.进一步优化,将指定子数组的尾部固定住。然后将0到该数组的位置的累加和求出来。减去所指定范围的最大值和最小值。只要这个子数组尾部前面的某个0到该位置的子数组符合要求就是答案。
这样问题就被大大简化成固定住尾部求前面符合要求的个数
5. 需要考虑到一种额外的情况就是判断所有的0到某个数是否在指定的范围之内。
6 将方法和归并排序相结合。需要额外加的部分在整个merge过程的上面

关于merge过程的具体实现

首先我们要了解明白的一点就是右边都是用来那做固定结尾的内容,左边就全是要在结尾固定的情况下选择合适开头。
在此种情况下我们需要申请两个变量,一个是windowL,另一个是windowR。
首先分析左边是有序的,右边也是有序的。最后所确定的[lower,upper]是不变的。所有随着右边的内容逐渐变大,筛选左边的[lower,upper]标准也在不断的变大。由此可以得出一个重要的结论在整个merge的过程中,windowL和windowR是不回退的。windowL和WindowR是一直向前走的一种行为。

暴力方法
int CountfrontsuminRange1(int arr[], int lower, int upper, int length)
{
	int res = 0;   //create variable to save the value we want

	int* arr3 = new int[length];
	arr3 = countRangeSum(arr, length);
	print(arr3, length);
	if (arr3[0]<upper && arr3[0]>lower) { res++; }
	for (int i = 1; i < length; i++)
	{
		if (arr3[i - 1] > lower && arr3[i - 1] < upper)
		{
			res++;
		}
		int nupper = arr3[i] - lower;
		int nlower = arr3[i] - upper;
		for (int j = 0; j < i; j++)
		{
			if (arr3[j] <= nupper && arr3[j] >= nlower)
			{
				res++;
			}
		}
	}
	return res;
}
采用归并排序的方法

int CountfrontsuminRange2(int arr[], int lower, int upper, int length, int L, int R)
{
	if (L == R)
	{
		return 0;
	}
	//完成前缀和的数组的准备工作
	int* arr2 = new int[length];
	arr2[0] = arr[0];
	for (int i = 1; i < length; i++)
	{
		arr2[i] = arr[i] + arr2[i - 1];
	}
	//通过return以及递归完成所有操作
	return process(arr2, lower, upper, length, L, R);

}

int process(int arr[], int lower, int upper, int length, int L, int R)
{

	int ans = 0;
	int mid = L + ((R - L) >> 1);
	//如果某个位置的数字刚好在对应的位置上
	if (L == R)
	{
		if (arr[L] > lower && arr[L] < upper)
		{
			ans++;
			cout << "这里应该一个没有";
		}
		else
		{
			return 0;
		}
	}
	int left = process(arr, lower, upper, length, L, mid);
	int right = process(arr, lower, upper, length, mid + 1, R);
	int merger = merge(arr, lower, upper, length, L, R, mid);
	cout << "L=" << L << "  " << "R=" << R << endl;
	cout << "left=" << left << "  " << "right=" << right << "  " << "merger=" << merger << endl;
	return left + right + merger;

}


int merge(int arr[], int lower, int upper, int length, int L, int R, int mid)
{

	int ans = 0;
	int windowL = L;
	int windowR = L;

	for (int i = mid + 1; i <= R; i++)
	{
		int min = arr[i] - upper;
		int max = arr[i] - lower;
		while (windowR < mid + 1 && arr[windowR] <= max)
		{
			windowR++;
		}
		while (windowL < mid + 1 && arr[windowL] < min)
		{
			windowL++;
		}
		ans += windowR - windowL;
	}
	int pr = mid + 1;
	int pl = L;
	int* help = new int[R - L + 1];
	int i = 0;
	while (pl <= mid && pr <= R)
	{
		help[i++] = arr[pl] > arr[pr] ? arr[pr++] : arr[pl++];
	}
	while (pl <= mid)
	{
		help[i++] = arr[pl++];

	}
	while (pr <= R)
	{
		help[i++] = arr[pr++];

	}
	for (int i = L; i < R - L + 1; i++)
	{
		arr[L + i] = help[i];
	}
	delete[] help;
	return ans;
}
归并排序题目改写总结

一共有四道题目:
小和问题
逆序对问题
左边大于右边两倍的问题
[lower, upper] 的问题

首先分析小和问题对归并的更改是比较简单,仅仅是在第一个while里面做了一个判断,以及在for循环的后面,也就是最后部分收集了有多少个前面的数小于后面的数的情况。由于归并将一个数字当成了两个部分,所以当前面数字不变的情况下,后面只要找到一个符合条件的数字,后面的所有数字就都是符合条件的

逆序对问题:
这个更改就是将两个指针pl和pr由从左向右走变为了从右向左走。然后是比较左右指针的内容将较大的放入help临时数组的最后面。这个时候如果发现一个pr大于pl那就意味着,pr大于pl左边的所有的数。
关于当两个数字相等的时候那个数字首先下去的问题.
经过自己在笔记上的研究发现当两个指针左右的数字相等的时候,右边的先下到help数组当中去。

三个问题都需要考虑一个比较重要的问题

就是当两个左右指针指向的内容相等的时候,是让左边的先下去help还是让右边的先下去help

在不加任何条件的单纯的一个归并排序的时候。当左右两个相等的时候。

help[i++] = arr[pl]>arr[pr] ? arr[pr]: arr[pl]
上面的语句就是在左右两个值相等的时候,下到help内的就是 左边的值
help[i++] = arr[pl]<arr[pr] ? arr[pl]: arr[pr]

上面的语句就是左右两个值相等的时候,先下右边
在排序的时候先下左边还是先下右边对排序都是没有影响的。

经过在笔记本上的验证全部都是先下右边
而且逆序对问题和大两倍问题都是从右边向左边这样算。
1.关于小和问题
小和问题
当两个数值相等的时候需要先下去的是右边

  1. 逆序对问题
    遇到左右两个数相等的时候先下右边

  2. 左边比右边大两倍的问题

快速排序——partiton

partition 主要的作用是依靠给定的一个参考值,将整组数据分为左边是比这个数字小的内容,中间是等于这个参考值的内容,后边是大于参考值的内容此时,这个参考值所在的位置就是排序最终应该出现的位置。

partition的具体操作

函数的参数包括三部分内容
一个是原始的数组
另一个是左边界L
另一个是右边界R
第一次左边分割为L——small
第二次右边分割为big——R
然后不断递归不断切分下去

最后的临届条件是L==R或者L>R的时候就返回。

  1. 申请三个变量,一个负责规定小于参考值的边界变量——small,一个负责大于参考值的边界量为——big
  2. 首先申请变量i逐个的检查每个元素的大小
  3. 当这个元素的值小于参考值的时候,和小于部分前的一个元素做交换。然后i++
  4. 当一个元素等于参考值的时候直接i++
  5. 当一个元素大于参考值的时候,将这个元素和最大区域的前一个元素做交换。然后此时i不变big++
  6. 然后继续可以检查从big前面的一个元素交换过来的内容。

堆排序——heapify 和 heapinsert——加强堆

首先介绍的应该是堆结构
堆结构分为大根堆和小根堆是一种类似于二叉树的结构
堆的结构等于完全二叉树,当节点进来的时候从左往右依次放入不能有空的地方。

使用数组的形式保存堆结构
堆结构的性质
如果子节点的序号为i,那么该节点的子节点为==(i-1)/2==
如果父节点的序号为i那么该父节点的左孩子为2i+1,该节点的右孩子等于2i+2

排序的临届条件
堆排序的流程:

  • 首先将进来的数组变成大根堆的结构
  • 大根堆结构的根的值是最大的
  • 将根和堆结构最后的一个值交换
  • heapsize–;
heapify

主要的作用就是更新检查根节点。当根节点在排序的过程中被替换的时候。启动heapify,找出自己子节点当中最大的节点和自己比较


largest = arr[2*i+2]==nullptr||arr[2*i+1]>arr[2*i+2]

加强堆

由语言api提供的堆有有一个缺点,就是如果我们在堆中放的是某种内容;
手动改写堆的代码讲解
1)建立反向索引表
2)建立比较器
3)核心内容在于各种结构的相互配合,非常容易错误

第一次写这一块内容的时候为什么搁置了呢。具体的原因就是不知道怎么给我自己建的类添加比较器,如果比较器是一个函数的话,还需要将函数作为参数传进来。但是可以将函数作为指针传进来
下面可以补充一点关于函数指针,函数对象相关的内容

关于加强堆出现的题目

非比较类排序

计数排序
基数排序

简单排序

插入排序

连接
no
yes
向函数中传入数组以及数组长度
从第一个元素开始循环遍历数组每个元素
检查是否当前循环元素是否大于前一个元素
当不符合条件时继续循环
说明当前的元素和前面的元素不符合从小到大的排列顺序

总结

  1. 关于代码的边界问题的研究和讨论
    有许多排序方法用到了 =递归以及跌代的方法其中比较重要的就是关于数据的边界问题
    首先研究比较重要的递归的边界问题
    首先以相对有代表性的《归并排序》为例
    根据R所表示的含义不同可以将算法的整个临届数据分为两种情况

  2. 当R的值和元素的个数相等的时候,比如有10个元素,R=10。
    mid=L((R-L)>>1);求从R到L范围内 mid中点的位置
    pL = 0;
    pr= mid+1;

  3. 当R的值和最后一个元素的下标相等的时候,当有10个元素的时候,R=9;
    存放排序临时结果的help数组的空间为[R-L+1] ==如果有10个元素 此时R =9 所以要容纳10个元素需要 L-R+1的空间 ==

二叉树

完全二叉树的概念:是按照严格从左到右的顺序依次填充的

满二叉树的概念:每一层都是满的,除去叶子节点之外,每一个节点都有两个孩子节点。满二叉树是一种特殊的完全二叉树。

二叉树的属性和性质

二叉树相关的题目

二叉树的遍历

递归遍历

在进行遍历的时候每一个数字都会进入三次。


void traversival(node* head)
{
		if(head == nullptr)
		{
			return null;
		}

		traversival(head->left);
		traversival(head->right);
		
}

非递归遍历

前序遍历

借助辅助容器栈来实现

void BinaryTree::traversalpre()
{
	
	bnode* header = head; //降头节点复制一份

	if (header == nullptr) //如果头节点为空则没有希望
	{
		return;
	}
	stack<bnode> one;  

	one.push(*header);
	while (!one.empty())
	{
		cout << one.top().data << endl;
		*header = one.top();
		one.pop();
		//用先右后左的顺序,如果二叉树是空的
		if (header->rnext != nullptr) 
		{
			one.push(*header->rnext);
		}
		if (header->lnext != nullptr)
		{
			one.push(*header->lnext);
		}
	}


}
中序遍历
void BinaryTree::traversalin()
{
	bnode* header = head;
	stack<bnode> sta;

	if (header != nullptr)
	{
		
		while (header != nullptr||!sta.empty())
		{
			if (header != nullptr)
			{
				sta.push(*header);
				header = header->lnext;
			}
			else
			{
				cout << sta.top().data << endl;
				header = sta.top().rnext;
				sta.pop();


			}
		}
	}

}
后序遍历
void BinaryTree::traversalpos()
{	
	stack<bnode> main;
	stack<bnode> help;
	bnode* header = head;
	
	if (header == nullptr)
	{
		return;
	}
	
	main.push(*header);
	while (!main.empty())
	{
		*header = main.top();
		help.push(main.top());
		main.pop();
		if (header->lnext != nullptr)
		{
			main.push(*header->lnext);
		}
		if (header->rnext != nullptr)
		{
			main.push(*header->rnext);

		}
	}
	while (!help.empty())
	{
		cout << help.top().data << endl;
		help.pop();
	}

}

总结:基本原理就是使用栈来模拟递归的压栈操作

宽度优先遍历

宽度优先遍历借助的辅助容器是队列。
过程相对来说也是比较简单
也是上去就把头给压进去,然后在头出来的时候,将该节点的左子树和右子树依次进入队列。
代码实现:

二叉树的序列化和返序列化

一共有三种遍历方式可以实现二叉树的序列化和反序列化

  • 前序遍历
  • 后序遍历
  • 宽度优先遍历

定义:有时候我们需要将一个二叉树保存下来,这个时候就要将二叉树变为唯一的一种字符串结构,然后还要可以将这个字符串可以完全还原回二叉树

序列化的思路以及实现的过程

前序遍历的序列化和反序列化
序列化
queue<string>* BinaryTree::preSerial()
{
	queue<string> *str = new queue<string>;
	bnode* header = head;
	pres(*str, header);
	return str;
}

void BinaryTree::pres(queue<string> &str,bnode* header)
{
	if (header == nullptr)
	{
		str.push("#");
		return;
	}
	str.push(to_string(header->data));
	pres(str, header->lnext);
	pres(str, header->rnext);
}

宽度优先遍历序列化

queue<string>* BinaryTree::levelSerial()
{
	queue<string> *str = new queue<string>;
	queue<bnode> main;
	bnode* header = head;
	if (header == nullptr)
	{
		
		return nullptr;
	}

	main.push(*header);
	while (!main.empty())
	{
		(*str).push(to_string(main.front().data));
		*header = main.front();
		main.pop();
		if (header->lnext != nullptr)
		{
			main.push(*header->lnext);
		}
		/*else
		{
			(*str).push("#");
		}*/

		if (header->rnext != nullptr)
		{
			main.push(*header->rnext);
		}
		/*else
		{
			(*str).push("#");
		}*/
		//if(header->lnext==nullptr||header->rnext) 
	}
	return str;
}

反序列化

bnode* BinaryTree::buildBylevel(queue<string>& str)
{
	if (str.size() == 0)//判断序列非空
	{
		return nullptr;
	}
	queue<bnode*> main;

		bnode* header = new bnode;
		header->data = stol(str.front());
		str.pop();
		main.push(header);
		bnode* cur = header;
		/*首先header是头,cur必须以一个代理的身份去以header的名义
		去连接下面的儿子部分此时cur 的地址要更新到*/


	while (!str.empty())
	{
		//bnode* cur = main.front();
		
		cur=main.front();
	
		main.pop();
		if (cur->lnext == nullptr)
		{
			bnode*n = new bnode(stol(str.front()));
			cur->lnext = n;
			main.push(n);
			str.pop();

		}
		if (cur->rnext == nullptr)
		{
			bnode* n = new bnode(stol(str.front()));
			cur->rnext = n;
			main.push(n);
			str.pop();
		}
	}
	return header;
}

反序列其中最主要需要研究的是:
如何通过逗号或者某些符号,比如逗号或者空格将string进行切分

关于二叉树递归套路的相关题目以及技巧

二叉树相关的题目

二叉树的子结构;

判断二叉树是否对称

bool isMirror(TreeNode* left,TreeNode* right)
    {
        if(left==NULL^right==NULL) 
        {
            return false;
        }
        if(right==NULL&&left==NULL)
        {
            return true;
        }
        return left->val==right->val&&isMirror(left->left,right->right)&&isMirror(left->right,right->left);
    }


···
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值