最新数据结构--排序_数据结构prev,大数据开发内存优化面试题

img
img

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

需要这份系统化资料的朋友,可以戳这里获取

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

	}

}

}

// 希尔排序(化简)
void ShellSort(int* a, int n)
{
int gap = n;
while (gap>1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n-gap; i++)
{
int end = i;
int tem = a[end + gap];
while (end <= 0)
{
if (a[end] > tem)
{
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tem;
}
}

}


**希尔排序的特性总结:**



> 
> 1. 希尔排序是对直接插入排序的优化。
> 
> 
> 2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
> 
> 
> 3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:
> 
> 
> **《数据结构(C语言版)》 --- 严蔚敏** 
> 
> 
> ![](https://img-blog.csdnimg.cn/341c299c44e34450b41d20ca55f5929c.png)
> 
> 
> **《数据结构-用面相对象方法与C++描述》 --- 殷人昆**
> 
> 
> ![](https://img-blog.csdnimg.cn/9f3365d41e8c486983bd640b91118336.png)
> 
> 
> 因为咋们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:![](https://img-blog.csdnimg.cn/535dd10268604f0fb55b4dd161e2983c.png)
> 
> 
> 4. 稳定性:不稳定
> 
> 
> 


###  选择排序


        每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。


#### **直接选择排序**


        在元素集合array[i]--array[n-1 ]中选择关键码最大(小)的数据元素。若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换 。在剩余的array[i]--array[n-2(array[i+1 ]--array[n-1 ])集合中,重复上述步骤,直到集合剩余1个元素 。


**动态演示:**


![](https://img-blog.csdnimg.cn/39c318a061484260af1d789a5d811e0c.gif)


 **代码实现:**



//交换
void Swap(int *p1, int *p2)
{
int tem=*p1;
*p1 = *p2;
p2 = tem;
}
// 选择排序
void SelectSort(int
a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int min = begin, max = begin;
for (int i = begin; i < end; i++)
{
if (a[min] < a[i])
min = i;
if (a[max]>a[i])
max = i;
}
Swap(&a[min], &a[begin]);
if (max == begin)
max = min;

	Swap(&a[max], &a[end]);
	begin++;
	end--;
}

}


**直接选择排序的特性总结:**



> 
> 1 . 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
> 
> 
> 2. 时间复杂度: O(N^2)
> 
> 
> 3. 空间复杂度: O(1 )
> 
> 
> 4. 稳定性:不稳定
> 
> 
> 


#### 堆排序


        堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。


**动态演示:**


**![](https://img-blog.csdnimg.cn/c9a8997c5d4a48edac7352caebc99dc9.gif)**


**代码实现:**



void AdjustDown(int* a, int n, int parent)
{
int minChild = parent * 2 + 1;
while (minChild < n)
{
// 找出小的那个孩子
if (minChild + 1 < n && a[minChild + 1] > a[minChild])
{
minChild++;
}

	if (a[minChild] > a[parent])
	{
		Swap(&a[minChild], &a[parent]);
		parent = minChild;
		minChild = parent * 2 + 1;
	}
	else
	{
		break;
	}
}

}

// O(NlogN)
void HeapSort(int
a, int n)
{
// 大思路:选择排序,依次选数,从后往前排
// 升序 – 大堆
// 降序 – 小堆
// 建堆 – 向下调整建堆 - O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}

// 选数 N*logN
int i = 1;
while (i < n)
{
	Swap(&a[0], &a[n - i]);
	AdjustDown(a, n - i, 0);
	++i;
}

}


### 交换排序


        所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。


#### 冒泡排序


        重复地走访过要排序的元素列,依次比较两个相邻的[元素](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb),如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。


**动态演示:**


**![](https://img-blog.csdnimg.cn/74d7c452147844dc975c4903387f0895.gif)**


**代码实现:**



// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n-i; j++)
{
if (a[j - 1] > a[j])
Swap(&a[j -1], &a[j]);
flag = 1;
}
if (flag == 0)
break;
}
}


**冒泡排序的特性总结:**  



> 
> 1 . 冒泡排序是一种非常容易理解的排序
> 
> 
> 2. 时间复杂度: O(N^2)
> 
> 
> 3. 空间复杂度: O(1 )
> 
> 
> 4. 稳定性:稳定
> 
> 
> 


#### **快速排序**


        快速排序是Hoare于1 962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。


**hoare版本** 


确定key值,right先向左找比key小的值,找到停止。然后left向右找比key大值,找到与right交换。再次循环相同操作,直到他们相遇,将相遇值与key值交换,返回相遇值。


**动态演示:**


![](https://img-blog.csdnimg.cn/ea984ac1585f489d9df877aa8956a176.gif)


**代码实现:**



// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);

int keyi = left;
while (left < right)
{
	// R找小
	while (left < right && a[right] >= a[keyi])
	{
		--right;
	}

	// L找大
	while (left < right && a[left] <= a[keyi])
	{
		++left;
	}

	if (left < right)
		Swap(&a[left], &a[right]);
}

int meeti = left;

Swap(&a[meeti], &a[keyi]);

return meeti;

}


**挖坑法**


设置一个坑位,将坑位的值赋值为key,right向左移找比key小值,找到将值放入坑位。left向右移找比key大值,找到将值放入坑位。相继操作到相遇为止,当相遇时key值放入相遇坑位,最后返回该值。


**动态演示:**


![](https://img-blog.csdnimg.cn/3270d971988448179bdfb48452858f96.gif)


**代码实现:**



int PartSort2(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);

int key = a[left];
int hole = left; //坑位
while (left<right)
{
	//右边先找小,填左坑位
	while (left < right&&a[right] >= a[key])
	{
		right--;
	}
	a[hole] = a[right];
	hole = right;
		
	//左边找大,填右坑位
	while (left < right&&a[left] <=  a[key])
	{
		left++;
	}
	a[hole] = a[left];
	hole = left;
}
a[hole] = a[key];
return hole;

}


**前后指针**


        在开始之前,prev与key位置相同,cur则在prev前面。这里主要分为两种情况:1.当cur的值小于key和prev的值也小于key时,prev先向前移动,cur再向前移动。2.当cur的值大于key值时,cur继续向后移,直到cur的值小于key值为止,然后prev再向前移动,直到prev的值大于key时,他们交换。直到cur大于right时,prve的值与key的值交换,最后跳出循环。


**动态演示:**


![](https://img-blog.csdnimg.cn/45863930e1a64c21b7dd766855cbe114.gif)


**代码实现:**



// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);

int key = left;
int prev = left;
int cur = left + 1;

while (cur<=right)
{
	//找小  prev先++ 相等跳出判断cur++,cur的值大于key值跳出判断cur++
	if (a[cur] < a[key] && ++prev != cur)
		Swap(&a[prev], &a[cur]);

	cur++;
}
Swap(&a[prev], &a[key]);

return prev;

}


        三个版本是都是单趟排序,我通过它的单趟排序观察出都是先确定key值,那么优选key的逻辑:1.随机选一个位置做key,2.针对有序,选正中间值做key,3.三数取中:通过比较第一个位置, 中间位置和最后一个位置,选取中间的位置即可。


        为什么需要三数取中呢?因为当一组数据是接近逆序时,我们发现right找小会将一组数据全部找完。这里我们hoare方法来举例:


![](https://img-blog.csdnimg.cn/9f85811d4f274b608ad4b17025617688.png)


三数取中


![](https://img-blog.csdnimg.cn/975fa0e5f05b4a21a220a973d9982c7b.png)


**代码实现:**



//三数取中
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[right] > a[mid])
return mid;
else if (a[left] < a[right])
return left;
else
return right;
}
else
{
if (a[right]>a[left])
return left;
else if (a[right] < a[mid])
return mid;
else
return right;

}

}


#### 快速排序递归实现


        上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。


![](https://img-blog.csdnimg.cn/16c624d2e23243e7a827ef9014d0e758.png)


         在实现递归的时候,我们发现最后三层递归,占了全部递归次数的85%左右,当递归在最后三层的时候已经非常接近有序了,所以我们选择用直接插入。


![](https://img-blog.csdnimg.cn/313517597c794312b25bbc7b0b70b81f.png)


**代码实现:**



// 快速排序递归实现
void QuickSort(int* a, int left, int right)
{

if (left >= right)
{
	return;
}
else if (right - left <= 8) //2^3 倒数三层(每次递归/2,递归三次 ),递归次数占接近85%
{
	InsertSort(a + left, right - left + 1); //直接插入
}
else
{
	int key = PartSort1(a, left, right);//key的作用:划分范围
	QuickSort(a, left, key - 1);//递归向左减少key
	QuickSort(a, key + 1, right);//递归向右增加key

}

}


#### 快速排序非递归实现


        这里非递归就将递归转化成非递归,通过栈(先进后出)的特征实现递归。因为递归就是操作系统的[函数栈帧](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb),我们可以模拟栈帧,在堆上开辟空间(malloc),赋予[栈](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)的功能。一个是操作系统的栈,一个是数据结构的栈。


![](https://img-blog.csdnimg.cn/4e176368e61e41df8e288b40f469b493.png)


        首先我们知道,在实现非递归之前我会用到数据结构中的栈,我们通过运用Stack.h,Stack.c构建的栈来实现快排的非递归。在这个过程中,我们需要联想到出栈入栈,寻找key值,始终记住这个过程只是通过栈将数据处理,排序的操作在单次排序进行。


![](https://img-blog.csdnimg.cn/78042a4eed64406bb9ca45cd774d4a19.gif)


**代码实现:**



// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st,left);
StackPush(&st,right);

while (!StackEmpty(&st))
{
	left = StackTop(&st);
	StackPop(&st);

	right = StackTop(&st);
	StackPop(&st);

	int key = PartSort1(a, left, right);

	if (right > key+1)
	{
		StackPush(&st, key+1);
		StackPush(&st, right);		
	}

	if (left < key-1)
	{
		StackPush(&st, left);
		StackPush(&st, key - 1);
	}
}
StackDestroy(&st);

}


**快速排序的特性总结:**



> 
> 1 . 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
> 
> 
> 2. 时间复杂度: O(N\*logN)
> 
> 
> 3. 空间复杂度: O(logN)
> 
> 
> 4. 稳定性:不稳定
> 
> 
> 


### 归并排序


        归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。


**动态演示:**


**递归**


**![](https://img-blog.csdnimg.cn/cfaf958ad2d94a4ca8d323a2f8cef72d.gif)**


**代码实现:**



void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;

int mid = (end + begin) / 2;

_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);

// 归并 取小的尾插
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
	if (a[begin1] <= a[begin2])
	{
		tmp[i++] = a[begin1++];
	}
	else
	{
		tmp[i++] = a[begin2++];
	}
}

while (begin1 <= end1)
{
	tmp[i++] = a[begin1++];
}

while (begin2 <= end2)
{
	tmp[i++] = a[begin2++];
}

// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));

}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int)*n);
if (temp == NULL)
{
perror(“malloc fail”);
exit(-1);
}

_MergeSort(a, 0, n-1, temp);

free(temp);
temp = NULL;

}


 **动态演示:**


**非递归**


**![](https://img-blog.csdnimg.cn/2edb8565f953402a96f697ed569c445d.gif)**



**代码实现:**



// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
perror(“malloc fail”);
exit(-1);
}

int  gap = 1;
while (gap < n)
{
	for (int j = 0; j < n; j += 2 * gap)
	{
		int begin1 = j, end1 = j + gap - 1;
		int begin2 = j + gap, end2 = j + 2 * gap - 1;

		//第一组end1越界
		if (end1>=n)
		{
			break;
		}
		//第二组全部越界
		if (begin2 >= n)
		{
			break;
		}
		//第二组部分越界
		if (end2 >= n)
		{
			end2 = n - 1;
		}
		int i = j;
		while (begin1 <= end1 && begin2 <= end2)
		{
			if (a[begin1] <= a[begin2])
			{
				tmp[i++] = a[begin1++];
			}
			else
			{
				tmp[i++] = a[begin2++];
			}
		}

		while (begin1 <= end1)
		{
			tmp[i++] = a[begin1++];
		}

		while (begin2 <= end2)
		{
			tmp[i++] = a[begin2++];
		}
		// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
		memcpy(a + j, tmp + j, (end2 - j + 1)*sizeof(int));
	}
	gap *= 2;
}


free(tmp);
tmp = NULL;

}


**归并排序的特性总结:**



> 
> 1 . 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
> 
> 
> 2. 时间复杂度: O(N\*logN)
> 
> 
> 3. 空间复杂度: O(N)
> 
> 
> 4. 稳定性:稳定
> 
> 
> 


### 排序算法复杂度及稳定性分析



![](https://img-blog.csdnimg.cn/e21494abd8dd458f844ca76301c62497.png)


 ![](https://img-blog.csdnimg.cn/c74dbd16fa744d519a743fff2fa17964.png)


#### 时间性能分析


**Test.c文件**


 我们在测试文件中,我们可以改变N,用于改变数据的多少,数据量越大则时间越久,我们可以通过不同的数据来观察各个排序所需时间。也能更直观的观察到时间复杂度,



#define _CRT_SECURE_NO_WARNINGS
#include “Sort.h”
#include “Stack.h”
// 测试排序的性能对比

void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int)N);
int
a2 = (int*)malloc(sizeof(int)N);
int
a3 = (int*)malloc(sizeof(int)N);
int
a4 = (int*)malloc(sizeof(int)N);
int
a5 = (int*)malloc(sizeof(int)N);
int
a6 = (int*)malloc(sizeof(int)*N);

for (int i = 0; i < N; ++i)
{
	a1[i] = rand();
	a2[i] = a1[i];
	a3[i] = a1[i];
	a4[i] = a1[i];
	a5[i] = a1[i];
	a6[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();

int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();

int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();

int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();

int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();

//int begin6 = clock();
//MergeSort(a6, N);
//int end6 = clock();

printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
//printf("MergeSort:%d\n", end6 - begin6);

free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
//free(a6);

}
void TestInsertSort()
{
int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
InsertSort(a, sizeof(a) / sizeof(int));
}

void TestShellSort()
{
//int a[] = { 9,8,7,6,5,4,3,2,1,0 };
int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}

void TestSelectSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
SelectSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}

void TestHeapSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestBubbleSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
BubbleSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}

void TestQuickSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSortNonR()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
//TestOP();
//TestBubbleSort();
//TestQuickSort();
TestQuickSortNonR();

return 0;

}


**Sort.c文件**



#include “Sort.h”
#include “Stack.h”
//交换
void Swap(int *p1, int *p2)
{
int tem=*p1;
*p1 = p2;
p2 = tem;
}
void PrintArray(int
a,int n)
{
for (int i = 0; i < n; i++)
{
printf(“%d “, a[i]);
}
printf(”\n”);
}
// 插入排序
void InsertSort(int
a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end=i;
int tem = a[end + 1];
while (end >= 0)

img
img
img

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

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

需要这份系统化资料的朋友,可以戳这里获取

zeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
//TestOP();
//TestBubbleSort();
//TestQuickSort();
TestQuickSortNonR();

return 0;

}


**Sort.c文件**



#include “Sort.h”
#include “Stack.h”
//交换
void Swap(int *p1, int *p2)
{
int tem=*p1;
*p1 = p2;
p2 = tem;
}
void PrintArray(int
a,int n)
{
for (int i = 0; i < n; i++)
{
printf(“%d “, a[i]);
}
printf(”\n”);
}
// 插入排序
void InsertSort(int
a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end=i;
int tem = a[end + 1];
while (end >= 0)

[外链图片转存中…(img-sdrSchwe-1715507870386)]
[外链图片转存中…(img-PkOwMh7I-1715507870387)]
[外链图片转存中…(img-RLeykiDz-1715507870387)]

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

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

需要这份系统化资料的朋友,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值