快速排序、堆排、二路归并排序、基数排序

快速排序

快速排序的思想:

  • 快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot)
  • 然后对数组进行分区操作,使 基准左边元素的值都小于基准值, 基准
    右边的元素值都大于基准值
    ,如此作为基准的元素调整到排序后
    的正确位置。

固定位置选取基准法

int Partion(int *arr, int low, int high)//找基准
{
	int tmp = arr[low];//tmp用来保存第一个元素的值
	while (low < high)
	{
		while (arr[high] > tmp&&low < high)
		{
			high--;
		}
		if (arr[high] < tmp)
		{
			arr[low] = arr[high];
		}
		else
		{
			break;
		}
		while (arr[low] < tmp&&low < high)
		{
			low++;
		}
		if (arr[low] > tmp)
		{
			arr[high] = arr[low];
		}
		else
		{
			break;
		}
	}
	arr[low] = tmp;
	return low;//low代表找到的基准的位置
}

递归实现快速排序

void Quick(int *arr, int start, int end)//递归
{
	int par = Partion(arr, start, end);//par代表找到的基准位置
	if (par > start + 1)//保证左边至少有两个数字
	{
		Quick(arr, start, par - 1);
	}
	if (par < end - 1)//保证右边至少有两个数字
	{
		Quick(arr, par + 1, end);
	}
}

非递归实现快速排序

利用栈来实现

void quicksort(int *arr, int len)//非递归
{
	int tmpsize = log((double)len) / log((double)2);
	int *stack = (int *)malloc(sizeof(int)*tmpsize * 2);//动态申请一个栈,用来存放下标
	assert(stack != NULL);
	int top = 0;//数组的下标
	int low = 0;
	int high = len - 1;


	int par = Partion(arr, low, high);//第一次的基准位置

	if (par > low + 1)//当左边至少有两个数字时,左边下标入栈
	{
		stack[top++] = low++;
		stack[top++] = par - 1;
	}
	if (par < high - 1)//当右边至少有两个数字时,右边下标入栈
	{
		stack[top++] = par + 1;
		stack[top++] = high--;
	}


	while (top > 0)//栈不为空
	{
		high = stack[--top];//出栈
		low = stack[--top];
		par = Partion(arr, low, high);//再次找基准并进行排序
		if (par > low + 1)//再次判断找到的基准 
		{
			stack[top++] = low++;
			stack[top++] = par - 1;
		}
		if (par < high - 1)
		{
			stack[top++] = par + 1;
			stack[top++] = high--;
		}
	}
	free(stack);//当栈为空代表已经全部进行了排序
	stack = NULL;
}

快速排序(递归):
好情况:O( n n l o g 2 n log_2 n )
坏情况:O( n 2 n^2 )

稳定性:不稳定

好情况:Partition 每次都划分得很均匀。
坏情况:当待排序数组是正序或者逆序的时候。 (退化成选择排序)

空间复杂度: O( l o g 2 n log_2 n )

随机选取基准法

void Quick(int *arr, int start, int end)//递归
{
	srand((unsigned int)time(NULL));
	Swap(arr, start, rand() % (end - start) + start);
	int par = Partion(arr, start, end);//par代表找到的基准位置
	if (par > start + 1)//保证左边至少有两个数字
	{
		Quick(arr, start, par - 1);
	}
	if (par < end - 1)//保证右边至少有两个数字
	{
		Quick(arr, par + 1, end);
	}
}

三分取中法

将中位数作为第一次的基准 ,即把中位数交换到low的位置

void SelectPivotMedianOfThree(int *arr, int low, int high)
{
	int mid = (high - low) / 2 + low;
	if (arr[mid] > arr[low])
	{
		Swap(arr, mid, low);//arr[mid]<=arr[low]
	}
	if (arr[mid] > arr[high])
	{
		Swap(arr, mid, high);//arr[mid]<=arr[high]
	}
	if (arr[low] > arr[high])
	{
		Swap(arr, low, high);
	}
}

快速排序的优化

(1)当待排序的区间中,待排序的个数小于某个数量级的时候,使用插入排序(插入排序越有序越快)
(2)聚集相同基准的元素

void FocusNumPar(int *arr, int low, int par, int high, int *left, int *right)
{
	if (low < high)
	{
		int parLeft = par - 1;
		for (int i = par - 1; i >= low; i--)
		{
			if (arr[i] == arr[par])
			{
				if (i != parLeft)
				{
					Swap(arr, i, parLeft);
					parLeft--;
				}
				else
				{
					parLeft--;
				}
			}
		}
		*left = parLeft;
		int parRight = par + 1;
		for (int i = par + 1; i <= high; i++)
		{
			if (arr[i] == arr[par])
			{
				if (i != parRight)
				{
					Swap(arr, i, parRight);
					parRight++;
				}
				else
				{
					parRight++;
				}
			}
		}
		*right = parRight;
	}
}

堆排序

堆排序的思想:

  1. 调整函数:调整指的是从最后一棵枝丫开始,从上往下调整(一次
    调整)
  2. 然后建立大根堆,而在这个过程当中需要不断的进行调整。
    一棵树的调整:
void Adjust(int *arr, int start, int end)//一棵树的调整
{
	int tmp = arr[start];
	for (int i = 2 * start + 1; i <= end; i = i * 2 + 1)
	{
		if (i < end&&arr[i] < arr[i + 1])//判断有没有右孩子   i保存孩子中值最大的下标
		{
			i++;
		}
		if (arr[i] > tmp)//将孩子结点中值最大的与父亲结点进行比较,保证父亲结点的值大于孩子结点的值
		{
			arr[start] = arr[i];
			start = i;
		}
		else
		{
			break;
		}
	}
	arr[start] = tmp;
}
void HeapSort(int *arr, int len)
{
	for (int i = (len - 1 - 1) / 2; i >= 0; i--)//大根堆的调整过程,从最后一个非叶子结点开始
	{
		Adjust(arr, i, len - 1);//每个非叶子结点都要调用这个函数
	}
	for (int i = 0; i < len - 1; i++)//先交换,后调整
	{
		int tmp = 0;
		tmp = arr[0];
		arr[0] = arr[len - 1 - i];
		arr[len - 1 - i] = tmp;
		Adjust(arr, 0, len - 1 - i - 1);//每次只调整0号位置的元素
	}
}

大根堆的建立过程:在这里插入图片描述大根堆建立完后的步骤就是交换和调整,先交换后调整。每次只用调整0号下标的值。
在这里插入图片描述堆排序:
好情况:O( n n l o g 2 n log_2n )
坏情况:O( n n l o g 2 n log_2n )

稳定性: 不稳定
空间复杂度:O(1)

二路归并排序

二路归并排序的思想:

即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并。

void Merge(int *arr, int len, int gap)//每组有序
{
	int *brr = (int *)malloc(sizeof(int) * len);//动态申请内存空间用来保存排好序的数组
	assert(brr != NULL);
	int i = 0;//brr的下标
	int start1 = 0;
	int end1 = start1 + gap - 1;
	int start2 = end1 + 1;
	int end2 = start2 + gap - 1 < len - 1 ? start2 + gap - 1 : len - 1;
	
	
	while (start2 < len) //当有两个归并段的时候
	{
		while (start1 <= end1 && start2 <= end2) //当两个归并段还没有比较完的时候
		{
			if (arr[start1] <= arr[start2])
			{
				brr[i++] = arr[start1++];
			}
			else
			{
				brr[i++] = arr[start2++];
			}
		}
		while (start1 <= end1)
		{
			brr[i++] = arr[start1++];
		}
		while (start2 <= end2)
		{
			brr[i++] = arr[start2++];
		}
		start1 = end2 + 1;//找两个新的归并段
		end1 = start1 + gap - 1;
		start2 = end1 + 1;
		end2 = start2 + gap - 1 < len - 1 ? start2 + gap - 1 : len - 1;
	}
	while (start1 < len)//只有一个归并段的时候
	{
		brr[i++] = arr[start1++];
	}
	for (int i = 0; i < len; i++)
	{
		arr[i] = brr[i];
	}
void MergeSort(int *arr, int len)//二路归并排序
{
	for (int i = 1; i < len; i *= 2)
	{
		Merge(arr, len, i);
	}
}


时间复杂度:
好情况:O( n n l o g 2 n log_2n )
坏情况:O( n n l o g 2 n log_2n )

空间复杂度:O(n)
稳定性稳定

基数排序

基数排序:又称“桶子法”排序,他是根据待排序的每一位上的数字进行入“桶”排序,桶的数量跟当前单个数字的取值范围有关。当前数字是十进制,单个数字就是 0-9.类似队列。

链表的实现:

typedef struct Node
{
	int data;
	struct Node *next;
}Node, *List;
void InitList(List plist)//初始化
{
	assert(plist != NULL);
	plist->next = NULL;
}
static Node *GetNode(int val)//得到一个结点
{
	Node *pGet = (Node *)malloc(sizeof(Node));
	assert(pGet != NULL);
	pGet->data = val;
	pGet->next = NULL;
	return pGet;
}
void Insert(List plist, int val)//尾插法
{
	Node *p = plist;
	while (p->next != NULL)
	{
		p = p->next;
	}
	Node *pGet = GetNode(val);
	p->next = pGet;
}
}



bool  DeleteFirst(List plist, int *rtv)//删除第一个结点
{
	assert(plist != NULL);
	Node *pDel = plist->next;
	if (pDel == NULL)
	{
		return false;
	}
	*rtv = pDel->data;
	plist->next = pDel->next;
	free(pDel);
	pDel = NULL;
	return true;
}

基数排序代码实现

int GetMaxBit(int  *arr, int len)//找出数组的最大值,算出最大值的位数
{
	int max = arr[0];
	int count = 0;
	for (int i = 1; i < len; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	while (max != 0)
	{
		count++;
		max /= 10;
	}
	return count;
}


int GetNum(int num, int figures)//得到第figures位的数字
{

	for (int i = 0; i < figures; i++)
	{
		num /= 10;
	}
	num = num % 10;
	return num;
}
void Radix(int *arr, int len, int figures)
{
	Node head[10];
	for (int i = 0; i < 10; i++)
	{
		InitList(&head[i]);//初始化
	}
	//1.入桶==>拿到数字判断第figures位为数字多少,入相应的桶里
	int tmp = 0;
	int i = 0;
	for (; i < len; i++)
	{
		tmp = GetNum(arr[i], figures);
		Insert(&head[tmp], arr[i]);
	}
	//2.出桶
	i = 0;
	for (int j = 0; j < 10; j++)//j代表桶的下标,一个桶出完再出另外一个桶
	{
		while (DeleteFirst(&head[j], &arr[i]))
		{
			i++;
		}
		void RadixSort(int *arr, int len)//基数排序(桶排序)(只考虑正数)
		{
			assert(arr != NULL);
			int count = GetMaxBit(arr, len);//确定入桶的次数
			for (int i = 0; i < count; i++)
			{
				Radix(arr, len, i);//i==>从右往左数第figures位的数字
			}
		}
	}
}

时间复杂度:O( r × n r×n )

r是趟数,n是数组的个数。

猜你喜欢

转载自blog.csdn.net/qq_43313035/article/details/84205461