2024年大数据最新数据结构--排序_数据结构prev(3),2024年最新大数据开发面试复习

img
img
img

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

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

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

动态演示:

代码实现:

// 冒泡排序
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 . 冒泡排序是一种非常容易理解的排序

  1. 时间复杂度: O(N^2)

  2. 空间复杂度: O(1 )

  3. 稳定性:稳定

快速排序

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

hoare版本

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

动态演示:

代码实现:

// 快速排序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值放入相遇坑位,最后返回该值。

动态演示:

代码实现:

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的值交换,最后跳出循环。

动态演示:

代码实现:

// 快速排序前后指针法
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方法来举例:

三数取中

代码实现:


//三数取中
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;
			
	}

}
快速排序递归实现

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

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

代码实现:

// 快速排序递归实现
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

	}

}
快速排序非递归实现

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

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

代码实现:

// 快速排序 非递归实现
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 . 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

  1. 时间复杂度: O(N*logN)

  2. 空间复杂度: O(logN)

  3. 稳定性:不稳定

归并排序

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

动态演示:

递归

代码实现:

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;
}

动态演示:

非递归

代码实现:

// 归并排序非递归实现
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)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

  1. 时间复杂度: O(N*logN)

  2. 空间复杂度: O(N)

  3. 稳定性:稳定

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

时间性能分析

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)
		{
			if (a[end] > tem)
			{
				a[end + 1] = a[end];
				end--;
			}		
			else
				break;			
		}
		a[end + 1] = tem;
	}
}

 希尔排序
//void ShellSort(int* a, int n)
//{
//	int gap = n;
//	while (gap>1)
//	{
//		gap = gap / 3 + 1;
//		for (int i = 0; i < gap; i++)
//		{
//			for (int j = i; j< n-gap; j += gap)
//			{
//				int end = j;
//				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;
//			}
//		}
//
//	}
//	
//}
//

// 希尔排序(化简)
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;
			}
	}

}

// 选择排序
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--;
	}

}

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(N*logN)
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;
	}
}

// 冒泡排序
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;
	}
}

//三数取中
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;
			
	}

}

// 快速排序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;
}


// 挖坑法
int PartSort2(int* a, int left, int right)
{


![img](https://img-blog.csdnimg.cn/img_convert/5c03c8d8968479b3bf74f5a6d5f4a28f.png)
![img](https://img-blog.csdnimg.cn/img_convert/6fbb25cd1fe694f9e14c3d18a8576e2c.png)
![img](https://img-blog.csdnimg.cn/img_convert/f1484ede13e11e31ac595599019f9957.png)

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

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

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

lse if (a[right] < a[mid])
			return mid;
		else
			return right;
			
	}

}

// 快速排序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;
}


// 挖坑法
int PartSort2(int* a, int left, int right)
{


[外链图片转存中...(img-qqLcWzY0-1715622959570)]
[外链图片转存中...(img-976qcKiE-1715622959570)]
[外链图片转存中...(img-weEVsMvq-1715622959570)]

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

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

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值