基础数据结构18:快速排序算法

1.快速排序方法(面试常考)

//***(面试笔试常考)
static int Partition(int* arr, int left, int right)
{
    
    
	assert(arr != NULL);
	
	//保证基准值在tmp里面
	int tmp = arr[left];
	while (left < right)//保证最少有两个值
	{
    
    
		while (left<right && arr[right]>tmp) right--;
		if (left == right)
		{
    
    
			break;
		}
		arr[left] = arr[right];
			
		while (left<right && arr[right]<=tmp) left++;
		if (left == right)
		{
    
    
			break;
		}
		arr[right] = arr[left];
	}
	arr[left] = tmp;
	return left;
}

请添加图片描述

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//#include "Lqueue.h"
#include "stack.h"
//八大排序   排序分为升序和降序   我们默认使用升序
//算法的描述  算法的实现  算法的评价(时间复杂度,空间复杂度,稳定性)
//什么是稳定性:如果排序前A在A`的前面,排序之后A孩子A`的前面,则排序算法稳定
//如何判断其稳定性:看算法中是否存在跳跃交换
//第一个排序算法:直接插入排序:每次从待排序队列中取一个值,放到已排序好的队列,再次保持有序,重复这样的动作,直到把待排序队列中的值全部取完  时间复杂度O(n^2) 空间复杂度O(1)  稳定的
//第二个排序算法:希尔排序(缩小增量排序):是一个特殊的直接插入排序,相当于多次调用直接插入排序,每一次的增量保持互素,并且最后一个增量一定位1,为1才能保证其完全有序   时间复杂度O(n^1.3~1.5) 空间复杂度O(1)  不稳定
//第三个排序算法:冒泡排序(沉石排序):两两比较,大的向后挪动,小的向前  时间复杂度O(n^2) 空间复杂度O(1)  稳定的(不存在跳跃交换)
//第四个排序算法:二路归并排序(非递归形式):两两合并成一个组,当合并后的组能容纳arr所有值的时候,则退出,因为此时已经组内完全有序   时间复杂度O(nlogn)  空间复杂度O(nlogn) 稳定
//第五个排序算法:简单选择排序:每一次将待排序序列中最小值和待排序序列中第一个值进行交换 直到完全有序即可  时间复杂度O(n^2) 空间复杂度O(1)  不稳定
//第六个排序算法:堆排序:将一维数组臆想成一个完全二叉树,再将其调整为大顶堆,再将根节点和尾节点进行交换,再次进行调整,这样循环往复,直至将其调整为完全有序   时间复杂度O(nlogn) 空间复杂度O(1)  不稳定  
//第七个排序算法:基数(桶)排序:低位优先,所有数据从低(个)位开始,依次放到对应的桶内(入队),再接着从桶内取出(出队),直到完全有序   时间复杂度O(dn)  空间复杂度O(n)   稳定吗
//第八个排序算法:快速排序(递归):(越乱越有序)从右向左找比基准值小的,向左放,再从左向右找比基准值大的,向右放,重复此过程  直到完全有序    时间复杂度O(nlogn)   空间复杂度O(logn)  不稳定

void InsertSort(int arr[], int len)
{
    
    
	assert(arr != NULL);
	if (arr == NULL)
	{
    
    
		return;
	}

	int count = 0;
	int tmp;
	int j;//将j的生存周期提高,保证break后的的代码arr[j+1] = tmp;有效
	for (int i = 1; i < len; i++)//每次从待排序队列中取的值
	{
    
    
		tmp = arr[i];//用tmp保存待插入的值
		for (j = i - 1; j >= 0; j--)//从右向左找不比tmp大的值
		{
    
    
			if (arr[j] > tmp)//如果比tmp大  则向右放一格
			{
    
    
				arr[j + 1] = arr[j];
				count++;
			}
			else//如果不比tmp大
			{
    
    
				break;
			}
		}

		arr[j + 1] = tmp;
	}
	printf("swap: %d\n", count);
}
void Show(int arr[], int len)
{
    
    
	for (int i = 0; i < len; i++)
	{
    
    
		printf("%d ", arr[i]);
	}
	printf("\n");
}


int count2 = 0;
static void Shell(int arr[], int len, int gap)
{
    
    
	int tmp;
	int j;//将j的生存周期提高,保证break后的的代码arr[j+1] = tmp;有效
	for (int i = gap; i < len; i++)//每次从待排序队列中取的值
	{
    
    
		tmp = arr[i];//用tmp保存待插入的值
		for (j = i - gap; j >= 0; j = j - gap)//从右向左找不比tmp大的值
		{
    
    
			if (arr[j] > tmp)//如果比tmp大  则向右放一格
			{
    
    
				arr[j + gap] = arr[j];
				count2++;
			}
			else//如果不比tmp大
			{
    
    
				break;
			}
		}

		arr[j + gap] = tmp;
	}
}
void ShellSort(int arr[], int len)
{
    
    
	//assert

	int gap[] = {
    
     5, 3, 1 };
	int lengap = sizeof(gap) / sizeof(gap[0]);
	for (int i = 0; i < lengap; i++)
	{
    
    
		Shell(arr, len, gap[i]);//5 3 1
	}


}

void BubbleSort(int arr[], int len)
{
    
    
	bool tag = true;//优化标记   如果当前这轮存在一次交换  则tag变成FALSE   
					 //当tag为true不就代表着不存在前面比后面大 
					 //则已经完全有序  那直接退出即可  剩余轮次不需要执行
	int count = 0;
	for (int i = 0; i < len - 1; i++)//少一轮
	{
    
    
		tag = true;
		for (int j = 0; j + 1 < len - i; j++)//j<len-1-i
		{
    
    
			if (arr[j] > arr[j + 1])//两两比较 前面大于后面 则交换两个值
			{
    
    
				tag = false;
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		count++;
		if (tag)
		{
    
    
			break;
		}
	}
	printf("跑了%d趟\n", count);
}

//一次融合的代码   时间复杂度O(n)
static void Merge(int arr[], int len, int gap)//gap->几几合并的几
{
    
    
	int* brr = (int*)malloc(sizeof(int) * len);//申请额外的辅助空间brr
	assert(brr != NULL);
	int i = 0;//i指向brr的下标

	//申请四个指针  用low1 high1来表示左边的组的边界  用low2 high2来表示右边的组的边界 
	int low1 = 0;
	int high1 = low1 + gap - 1;
	int low2 = high1 + 1;
	int high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;
	//low2+gap-1是我应得的   len如果大于我应得的  则该给我多少就给多少  如果小则将你剩余的都给我

	while (low2 < len)//我抓的两个组都存在   low2<len则代表右手没抓空  而右手没抓空 则左手肯定也没抓空  则证明两手都抓中了
	{
    
    
		while (low1 <= high1 && low2 <= high2)//两个组都还有数据  则接着比较向下取到brr内
		{
    
    
			if (arr[low1] <= arr[low2])
			{
    
    
				brr[i++] = arr[low1++];
			}
			else
			{
    
    
				brr[i++] = arr[low2++];
			}
		}
		//此时肯定有一组数据没了 那么将另一组数据直接挪到brr内即可
		//但此时需要判断左右手哪个抓的组里没数据了
		while (low1 <= high1)//左手抓的组里面还有数据 在直接挪到brr内
		{
    
    
			brr[i++] = arr[low1++];
		}
		while (low2 <= high2)//右手抓的组里面还有数据 在直接挪到brr内
		{
    
    
			brr[i++] = arr[low2++];
		}

		low1 = high2 + 1;
		high1 = low1 + gap - 1;
		low2 = high1 + 1;
		high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;

	}

	//此时肯定不能同时两手都抓到组   而且可以得到一个信息  那就是(只可能右手抓的组是空的)
	//左手抓上一个组 右手抓空  接下来只需要将左手的组原样挪下来即可
	while (low1 < len)//左手抓的组里面还有数据 在直接挪到brr内
	{
    
    
		brr[i++] = arr[low1++];
	}

	//最后一步:将辅助空间brr里的数据全部重新拷贝到arr里
	for (int i = 0; i < len; i++)
	{
    
    
		arr[i] = brr[i];
	}
	free(brr);//最后记得将malloc来的brr释放掉  避免内存泄露
	brr = NULL;

}

void MergeSort(int arr[], int len)
{
    
    
	//assert
	for (int i = 1; i < len; i *= 2)//11融合 22融合  44融合  88融合  直到融合后的组包含了arr所有值
	{
    
    
		Merge(arr, len, i);//logn次 * O(n)  = nlogn
	}
}

//时间复杂度 O(n^2)  空间复杂度O(1) 稳定吗?  不稳定
void SelectSort(int* arr, int len)
{
    
    
	//assert
	int minindex;//minindex 保存的是最小值的下标

	for (int i = 0; i < len - 1; i++)//趟数  总数少一趟
	{
    
    
		minindex = i;//每趟循环一进来  我们首先认为待排序序列的第一个值是最小值 所以minindex先赋值为i
		for (int j = i + 1; j < len; j++)//从待排序序列的第二个值开始和arr[minindex]比较
		{
    
    
			if (arr[j] < arr[minindex])//如果找到更小的数  则minindex重新赋值为新的最小值的下标
			{
    
    
				minindex = j;
			}
		}
		//此时for循环执行结束  代表着minindex保存的就是最小值的下标

		//直接进行交换
		if (i != minindex)
		{
    
    
			int tmp = arr[i];//因为i保存的是待排序序列第一个值
			arr[i] = arr[minindex];
			arr[minindex] = tmp;
		}
	}
}


void HeapAdjust(int arr[], int start, int end)
{
    
    
	int tmp = arr[start];

	for (int i = start * 2 + 1; i <= end; i = start * 2 + 1)//语句3 精髓  时间复杂度O(nlogn)的原因
	{
    
    
		if (i < end && arr[i] < arr[i + 1])//有右孩子 并且右孩子比左孩子大 
		{
    
    
			i++;
		}
		//如果if为假   代表要么右孩子不存在  要么右孩子存在 但是小于左孩子
		//此时 i保存的是左右孩子中较大的值的下标
		//接着让父子比较

		if (arr[i] > tmp)
		{
    
    
			arr[start] = arr[i];
			start = i;
		}
		else
		{
    
    
			break;
		}
	}
	//此时for循环推出了,要么此时触底  要莫if(arr[i]>tmp)为假  break跳出循环

	arr[start] = tmp;
}

void HeapSort(int arr[], int len)
{
    
    
	//1.先调整成大顶堆  从最后一个非叶子节点开始
	for (int i = (len - 1 - 1) / 2; i >= 0; i--)//O(n)
	{
    
    
		HeapAdjust(arr, i, len - 1);//(logn)    
	}//两个一乘   则时间复杂度O(nlogn)

	//根节点和尾节点交换
	for (int i = 0; i < len - 1; i++)//O(n)
	{
    
    
		int tmp = arr[0];
		arr[0] = arr[len - 1 - i];//9 8 7 6 5 4 3 2 1
		arr[len - 1 - i] = tmp;

		HeapAdjust(arr, 0, (len - 1 - i) - 1);//(len-1-i)相当于最后一个节点的下标  然后最后一个节点不参与计算 则再-1
	}
}

//获取数组中最大值的位数
static int Get_figure(int* arr, int len)
{
    
    
	//assert
	//获取最大值是多少
	int tmp = 0;
	for (int i = 0; i < len; i++)
	{
    
    
		if (arr[i] > tmp)
		{
    
    
			tmp = arr[i];
		}
	}

	//获取tmp的位数        12345
	int count = 0;//保存位数
	while (tmp != 0)
	{
    
    
		count++;
		tmp /= 10;
	}

	//次数 while循环执行结束  count就保存着tmp的位数 
	return count;
}

//以fin的规则取n对应的值    
//123 0  -> 3
//234 2  -> 2
//345 3  -> 0
//12345 3 -> 12345->1234->123->12 %10 = 2 
static int Get_Num(int n, int fin)
{
    
    
	for (int i = 0; i < fin; i++)
	{
    
    
		n /= 10;
	}
	//此时 for循环执行结束 代表着n扔掉了fin个最低位   此时取n的最低位即可

	return n % 10;
}

//作业1.不用二维静态数组   而是使用队列的形式去实现基数排序
//以fin为来排序一次   放到桶内(入队)  从桶内再取出(出队)
static void Radix(int* arr, int len, int fin) //时间复杂度O(n)
{
    
    
	int bucket[10][20] = {
    
     0 };//十个桶  每个桶初始容量20个
	int num[10] = {
    
     0 };//num数组  保存着每一个桶的有效值个数

	for (int i = 0; i < len; i++)//从0号下标开始 依次放入桶内
	{
    
    
		int index = Get_Num(arr[i], fin);//获取一下arr[i] 到底应该放入哪个桶内
		bucket[index][num[index]] = arr[i];//放入对应的桶内
		num[index]++;//让对应的桶内个数++
	}
	//此时for循环执行结束 代表着数据全部放到了桶内   

	int k = 0;//保存着向arr里存放的空间下标
	//此时 接着应该从0-9号桶  依次取出数据
	for (int i = 0; i <= 9; i++)//i保存的是桶号 //10 * O(n)
	{
    
    
		for (int j = 0; j < num[i]; j++)//j代表着从桶内0号下标开始取值  直到取完
		{
    
    
			arr[k++] = bucket[i][j];
		}
	}
}

//static void Radix_Queue(int *arr, int len, int fin) //时间复杂度O(n)
//{
    
    
//	Head Arr_queue[10];
//	for(int i=0; i<10; i++)
//	{
    
    
//		Init_lqueue(&Arr_queue[i]);
//	}
//	//此时10个链式队列桶全部初始化完毕
//
//	for(int i=0; i<len; i++)
//	{
    
    
//		int index = Get_Num(arr[i], fin);
//		Push(&Arr_queue[index], arr[i]);
//	}
//
//	int k = 0;//保存着从桶内取出后向arr中哪个位置放
//	for(int i=0; i<10; i++)//代表的是桶号
//	{
    
    
//		while(!IsEmpty(&Arr_queue[i]))
//		{
    
    
//			/*int tmp;
//			Pop(&Arr_queue[i], &tmp);
//			arr[k++] = tmp;*/
//			Pop(&Arr_queue[i], &arr[k++]);
//		}
//	}
//}



//void RadixSort(int arr[], int len) //时间复杂度O(dn)
//{
    
    
//	//循环次数   最大值的位数来控制
//	int count = Get_figure(arr, len);
//
//	for(int i=0; i<count; i++)//d  代表着最大值的位数
//	{
    
    
//		Radix_Queue(arr, len, i);//O(n)
//	}
//}

//这个函数  必须背过   面试笔试的时候 如果要考快排  你直接写这个函数就行了
static int Partition(int* arr, int left, int right)
{
    
    
	//保存基准值到tmp里
	int tmp = arr[left];

	//重复三规则
	while (left < right)//保证最少有两个值
	{
    
    
		while (left<right && arr[right] > tmp)//从右向左找比tmp小的  那如果比tmp大 则right-- 一下
			right--;
		if (left == right)//left == right 代表 找到了基准值该放的位置
		{
    
    
			break;
		}
		arr[left] = arr[right];//不然 就是找到了比tmp小的值   放到左侧即可

		while (left < right && arr[left] <= tmp)//从左向右找比tmp大的  那如果比tmp小 则left++ 一下
			left++;
		if (left == right)//left == right 代表 找到了基准值该放的位置
		{
    
    
			break;
		}
		arr[right] = arr[left];//不然 就是找到了比tmp大的值   放到右侧即可
	}
	//此时 left == right

	arr[left] = tmp;//arr[right] = tmp;//将基准值放到left和right重叠的位置上
	return left; //return right 将基准值下标返回
}

//三数取中法   将第一个值和不大不小的值做一个交换 3 6 9   9 6 3
void ThreeNumGetMid(int arr[], int left, int mid, int right)
{
    
    
	//(3 6 9)  (9 6 3)
	/*if((arr[mid] > arr[left] && arr[mid] < arr[right]) ||
	(arr[mid] > arr[right] && arr[mid] < arr[left]))
	{
		int tmp = arr[left];
		arr[left] = arr[mid];
		arr[mid] = tmp;
	}*/

	if (arr[left] > arr[right])//左边的值大于右边的值
	{
    
    
		int tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
	}//代表着 left和right中较小的值保存在左端   

	if (arr[left] < arr[mid])
	{
    
    
		int tmp = arr[left];
		arr[left] = arr[mid];
		arr[mid] = tmp;
	}//此刻能保证left保存的值  肯定比mid的大

	if (left > right)
	{
    
    
		int tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
	}
}
static void Quick(int* arr, int left, int right)
{
    
    
	//第一个优化:如果right-left判断出数据量特别小  直接改道去执行冒泡或者直接插入算法
	//第二个优化:三数取中法   取第一个值和 最中间的值 和最后一个值   判断一下  找一个不大不小的值当做我们的基准值 
	//第三个优化:主要针对快排已经有序,为了防止完全有序, 我们自己打乱一下
	if (right - left <= 100)
	{
    
    
		return BubbleSort(arr, right - left);
	}

	int mid = (left + right) / 2;//(right-left)/2+left
	ThreeNumGetMid(arr, left, mid, right);

	if (left < right)//确保需要排序的数组值最少有2个
	{
    
    
		int par = Partition(arr, left, right);
		if (left < par - 1)//保证基准值左边的数据最少有两个
		{
    
    
			Quick(arr, left, par - 1);
		}

		if (par + 1 < right)//保证基准值右边的数据最少有两个
		{
    
    
			Quick(arr, par + 1, right);
		}
	}

	/*  上面代码和这处代码  记住一个即可
	if(left < right)//确保需要排序的数组值最少有2个
	{
		int par = Partition(arr, left, right);
		Quick(arr, left, par-1);
		Quick(arr, par+1, right);
	}
	*/

}

static void Quick_Stack(int* arr, int left, int right)
{
    
    
	Stack st;
	Init_stack(&st);

	if (left < right)
	{
    
    
		int par = Partition(arr, left, right);
		if (left < par - 1)
		{
    
    
			Push(&st, left);
			Push(&st, par - 1);
		}
		if (par + 1 < right)
		{
    
    
			Push(&st, par + 1);
			Push(&st, right);
		}
	}
	//此时 代表着第一个partition函数执行后,划分的两段数据,
	//将左右两段中数据量大于等于2的段的两段下标入到栈内,让接下来继续划分

	//接下来从stack内每次取两个值   用作partition的left和right的值
	while (!IsEmpty(&st))//如果stack不为空   代表里面的有效值个数是2的倍数
	{
    
    
		int p, q;//p,q分别代表着下一次partition时候的left和right
		Pop(&st, &q);
		Pop(&st, &p);

		int par = Partition(arr, p, q);
		if (p < par - 1)
		{
    
    
			Push(&st, p);
			Push(&st, par - 1);
		}
		if (par + 1 < q)
		{
    
    
			Push(&st, par + 1);
			Push(&st, q);
		}
	}
}
void QuickSort(int arr[], int len)
{
    
    
	//assert
	Quick_Stack(arr, 0, len - 1);
}
//作业2: 用非递归的形式去实现快速排序(栈)



int main()
{
    
    
	//int arr[] = {1,3,2,4,6,5,7,9,8};
	int arr[] = {
    
     215, 217, 21, 127, 125, 5, 1, 2, 7, 888, 66, 88, 33, 18, 81 };
	int len = sizeof(arr) / sizeof(arr[0]);

	//InsertSort(arr, len);
	//ShellSort(arr, len);
	//printf("swap = %d\n", count2);
	//BubbleSort(arr, len);
	//MergeSort(arr, len);
	//SelectSort(arr, len);
	//HeapSort(arr, len);
	//RadixSort(arr, len);
	QuickSort(arr, len);
	Show(arr, len);


	return 0;
}

递归算法较为难,尽量理解

猜你喜欢

转载自blog.csdn.net/qq_48580892/article/details/120068991