八大排序详解||冒泡排序||选择排序||插入排序||shell(希尔排序)||快速排序(多种优化)||堆排序||归并排序||基数排序(桶排序)||效率总结

目录

1.冒泡排序

2.选择排序

3.直接插入排序

4.shell排序

5.快速排序

5.1递归版本

5.2栈版本

5.3随机选取基准法

5.4三分取中法

5.5少量元素直接插入法

5.6聚集相同元素法

6.堆排序

7.归并排序

8.基数排序

9.时间复杂度、空间复杂度、稳定性总结


这篇文章的大多图片来自网上一些大神的分享,在此感谢!!

1.冒泡排序

思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。

  1. 第一次比较:首先比较第一和第二个数,将小数放在前面,将大数放在后面。
  2. 比较第2和第3个数,将小数 放在前面,大数放在后面。
  3. 如此继续,直到比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成
  4. 在上面一趟比较完成后,最后一个数一定是数组中最大的一个数,所以在比较第二趟的时候,最后一个数是不参加比较的。
  5. 在第二趟比较完成后,倒数第二个数也一定是数组中倒数第二大数,所以在第三趟的比较中,最后两个数是不参与比较的。
  6. 依次类推,每一趟比较次数减少依次减少。
void BubbleSort(int* arr, int len)
{
	int i, j, flag;
	int temp;
	for (i = len - 1; i >= 0; --i)
	{
		flag = 0;
		for (j = 1; j <= i; ++j)
		{
			if (arr[j] < arr[j - 1])
			{
				temp = arr[j];
				arr[j] = arr[j - 1];
				arr[j - 1] = temp;
				flag = 1;
			}
		}
		if (flag == 0)
			return;
	}
}

2.选择排序

思路:

1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

3.重复第二步,直到所有元素均排序完毕

动图展示:

void SelectSort(int* arr, int len)
{
	int temp = 0;
	for (int i = 0; i <= len - 1; ++i)
	{
		for (int j = i + 1; j < len ; ++j)
		{

			//将第i小的元素放入位置i
			if (arr[j] < arr[i])
			{
				temp = arr[j];
				arr[j] = arr[i];
				arr[i] = temp;
			}
		}
	}
}

3.直接插入排序

思路:选取一个基准,然后和之前的数据依次开始比较,如果数据比基准大,则将数据后移,如果比基准小,将数据插入到此位置。

动图展示:

void InsertSort(int* arr, int len)
{
	int i, j, temp;
	for (i = 1; i < len; ++i)
	{
		temp = arr[i];
		j = i -1;
		while (j >= 0 && temp < arr[j])
		{
			arr[j + 1] = arr[j];
			--j;
		}
		arr[j + 1] = temp;

	}
}

4.shell排序

shell排序,是以数组下标为准,按照固定的差值将元素分成几组,然后每组插入排序进行排序。然后将差值依次减少,当差值减少为1的时候,元素只剩一组,再次使用插入排序,就可以使整个数组有序。

void InsertSort(int* arr, int gap,int i)
{
	int insertEd = arr[i];
	int j = i - gap;
	while (j >= 0 && insertEd < arr[j])
	{
		arr[j + gap] = arr[j];
		j -= gap;
	}
	arr[j + gap] = insertEd;
}
void Shell(int* arr, int len)
{
	int gap = len / 2;
	while (gap > 0)
	{
		for (int i = gap; i < len; ++i)
		{
			InsertSort(arr, gap, i);
		}
		gap /= 2;
	}
}

5.快速排序

快速排序,是以数组中的某一元素为基准,(如果要求数组从小到大排列)将比基准大的元素放在基准的右边,比基准小的元素放在基准的左边。

经典快速排序算法的图解:

根据辅助空间的不同我们可以将快速排序分为递归版快速排序和栈版的快速排序。

5.1递归版本

//num1和num2为数组的开始和结束下标
void QuickSort(int* arr, int num1, int num2)
{
	if ((num1 > num2) || ((num2 - num1) < 1))
	{
		return;
	}
	int low = num1;
	int high = num2;
	int temp = arr[low];
	while (low < high)
	{
		while ((low < high) && (temp < arr[high]))
		{
			--high;
		}
		if (low < high)
		{
			arr[low] = arr[high];
			++low;
		}
		while ((low < high) && (temp > arr[low]))
		{
			++low;
		}
		if (low < high)
		{
			arr[high] = arr[low];
			--high;
		}
	}
	arr[high] = temp;
	QuickSort(arr, num1, high - 1);
	QuickSort(arr, high + 1, num2);
}

5.2栈版本

int Quick(int* arr, int num1, int num2)
{
	int low = num1;
	int high = num2;
	int temp = arr[low];
	while (low < high)
	{
		while ((low < high) && (temp < arr[high]))
		{
			--high;
		}
		if (low == high)
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
			++low;
		}
		while ((low < high) && (temp > arr[low]))
		{
			++low;
		}
		if (low == high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
			--high;
		}
	}
	arr[high] = temp;
	return low;
}


void QuickSort(int* arr, int len)
{
	//申请辅助空间,O(logn)
	int typeSize = (int)(log((double)len)) / (log((double)2));
	int* stack = (int*)malloc(sizeof(int) * typeSize * 2);
	assert(stack != NULL);
	int top = 0;
	int start = 0;
	int end = len - 1;
	//得到基准最后放入的位置
	int index = Quick(arr, start, end);
	//以基准为分界线,分解成两个小数组,将两个小数组的左右下标存入栈
	if (index > (start + 1))
	{
		stack[top++] = index - 1;
		stack[top++] = start;
	}
	if ((index + 1) < end)
	{
		stack[top++] = end;
		stack[top++] = index + 1;
	}
	while (top > 0)
	{
		//继续从小数组中选取基准进行排序
		start = stack[--top];
		end = stack[--top];
		index = Quick(arr, start, end);
		if (index > (start + 1))
		{
			stack[top++] = index - 1;
			stack[top++] = start;
		}
		if ((index + 1) < end)
		{
			stack[top++] = end;
			stack[top++] = index + 1;
		}
	}
	free(stack);
}

5.3随机选取基准法

当一个数组大到小有序,如果要求数组从小到大有序的话,我们如果像上面一样每次从头选取基准,将是非常耗时的,这时我们不妨从数组中随机的选取一个值来当作基准来进行排序。

void QuickSort(int* arr, int low, int high)
{
	if (low > high || (high - low) < 1)
	{
		return;
	}
	srand((unsigned int)time(NULL));
	Swap(arr, low, rand() % (high - low) + low);
	//后面的代码同递归版的QuickSort
}

Swap(arr, low, rand() % (high - low) + low);

这句代码我们来分析一下:

假设rand() % (high - low) + low产生的随机数为index,Swap函数将数组从low到index反转,然后还是以数组的第一个元素为基准,前面的一小段数组是反转过的,就相当于是以arr[index]为基准来进行排序的。

5.4三分取中法

三分取中法即每次以数组的中间值来作为基准值来进行排序。

快速排序是一种交换类的排序,它同样是分治法的经典体现。在一趟排序中将待排序的序列分割成两组,其中一部分记录的关键字均小于另一部分。然后分别对这两组继续进行排序,以使整个序列有序。在分割的过程中,枢纽值的选择至关重要,采取了三位取中法,可以很大程度上避免分组"一边倒"的情况(一边元素较多,一边元素较小)。

void SelectPivotMedianOfThree(int *arr,int low,int high)
{
	int mid =(high-low)/2+low;
	if(arr[mid]>arr[low])
	{
		Swap(arr,mid,low);
	}
	if(arr[low]>arr[high])
	{
		Swap(arr,high,low);
	}
	if(arr[mid]>arr[high])
	{
		Swap(arr,mid,high);
	}
}
//将此代码添加在快速排序选基准值之前即可。

上述这段代码(和三个数的排序代码有点相似)的意思为:

取数组中的首位数,中位数以及末尾数,然后取这三个数中第二大的数作为枢纽值。

5.5少量元素直接插入法

快速排序会一直将数据进行分割,当数组的大小到一定的程度的时候,直接插入的排序方法是要优于快速排序的,我们可以在每次快速排序之前,对数组的大小进行判断,当小于某一限定值的时候,可以采取直接插入法来进行排序。

//在每一次进入快速排序中的时候,我们都需要加以判断。
if((high-low)<Min)
{
	InsertSort(arr,low,high);
	return;
	
}

5.6聚集相同元素法

在每次排序结束的时候,我们可以将左右两边与基准值相同的元素,分别放在基准值最终位置的两边,这样可以极大的降低时间复杂度。

int Partion(int* arr, int low, int high)//取一次快排的中间值
{
	int temp = arr[low];
	while (low < high)
	{
		while (low<high && arr[high]>temp)
		{
			high--;
		}
		if (low >= high)
		{
			break;
		}
		else
		{
			arr[low] = arr[high];
			++low;
		}
		while (low < high && arr[low] < temp)
		{
			low++;
		}
		if (low >= high)
		{
			break;
		}
		else
		{
			arr[high] = arr[low];
			high--;
		}

	}
	arr[low] = temp;
	return low;

}
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);
				}
				else
				{
					parRight++;
				}
			}
		}
		*right = parRight;
	}


}
void QuickSort(int* arr, int num1, int num2)
{
	if ((num1 > num2) || (num2 - num1) < 1)
	{
		return;
	}
	int low = num1;
	int high = num2;
	int par = Partion(arr, low, high);
	int left = 0;
	int right = 0;
	FocusNumpar(arr, low, par, high, &left, &right);
	if (left >= low + 1)
	{
		QuickSort(arr, low, left);
	}
	if (right < high - 1)
	{
		QuickSort(arr, right, high);
	}
}

6.堆排序

堆排序是利用大根堆小根堆这两种数据结构来对数组进行排序的,还不了解堆的可以参考我的博文:链接

void Heap(int* arr, int start, int end)
{
	int i = 2 * start + 1;
	int temp;
	while (i < end)
	{
		//取左右孩子中较小的
		if ((i + 1) < end && (arr[i + 1] <arr[i]))
		{
			++i;
		}
		if (arr[i] > arr[start])
		{
			break;
		}
		temp = arr[i];
		arr[i] = arr[start];
		arr[start] = temp;
		start = i;
		i = 2 * start + 1;
	}
}
void HeapSort(int* arr, int len)
{
	//从下到上对堆进行调整
	for (int i = (len - 1) / 2; i >= 0; --i)
	{
		Heap(arr, i, len - 1);
	}
	int temp = 0;
	for (int i = len - 1; i >= 0; --i)
	{
		//将堆顶元素放入末尾
		temp = arr[0];
		arr[0] = arr[i];
		arr[i] = temp;
		//对堆顶元素进行调整
		Heap(arr, 0, i);
	}
}

7.归并排序

归并排序是分治思想的典型的一个应用。整体过程如下图:

/*
 * 将一个数组中的两个相邻有序区间合并成一个
 *
 * 参数说明:
 *     a -- 包含两个有序区间的数组
 *     start -- 第1个有序区间的起始地址。
 *     mid   -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。
 *     end   -- 第2个有序区间的结束地址。
 */
void merge(int a[], int start, int mid, int end)
{
	int* tmp = (int*)malloc((end - start + 1) * sizeof(int));    // tmp是汇总2个有序区的临时区域
	int i = start;            // 第1个有序区的索引
	int j = mid + 1;        // 第2个有序区的索引
	int k = 0;                // 临时区域的索引

	while (i <= mid && j <= end)
	{
		if (a[i] <= a[j])
			tmp[k++] = a[i++];
		else
			tmp[k++] = a[j++];
	}

	while (i <= mid)
		tmp[k++] = a[i++];

	while (j <= end)
		tmp[k++] = a[j++];

	// 将排序后的元素,全部都整合到数组a中。
	for (i = 0; i < k; i++)
		a[start + i] = tmp[i];

	free(tmp);
}

/*
 * 归并排序(从上往下)
 *
 * 参数说明:
 *     a -- 待排序的数组
 *     start -- 数组的起始地址
 *     endi -- 数组的结束地址
 */
void merge_sort_up2down(int a[], int start, int end)
{
	if (a == NULL || start >= end)
		return;

	int mid = (end + start) / 2;
	merge_sort_up2down(a, start, mid); // 递归排序a[start...mid]
	merge_sort_up2down(a, mid + 1, end); // 递归排序a[mid+1...end]

	// a[start...mid] 和 a[mid...end]是两个有序空间,
	// 将它们排序成一个有序空间a[start...end]
	merge(a, start, mid, end);
}

8.基数排序

基数排序又叫做桶排序,它的操作步骤如下图所示。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
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;
}
//插入到链表中
bool Insert(List plist, int val)
{
	Node* p = plist;
	while (p->next != NULL)
	{
		p = p->next;
	}
	Node* pGet = GetNode(val);
	p->next = pGet;
	return true;
}
//从链表中删除元素
bool DeleteFirst(List plist, int* rtv)
{
	Node* pDel = plist->next;
	if (pDel == NULL)
	{
		return false;
	}
	*rtv = pDel->data;
	plist->next = pDel->next;
	free(pDel);
	pDel = NULL;
	return true;
}
//123 === > 3位数  n   123/10 12/10 = 1   1/10 = 0 
int GetMaxBit(int* arr, int len)
{
	//1、你要找到数组的最大值
	int max = arr[0];
	for (int i = 1; i < len; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	//2、算出最大值的位数
	int count = 0;
	while (max != 0)//123
	{
		count++;//1  2  3
		max /= 10;//12  1   0
	}
	return count;
}
//123--->  123%10   123/10%10==2   123/10/10%10-->1
int GetNum(int num, int figures)
{
	for (int i = 0; i < figures; i++)
	{
		num /= 10;
	}
	return num % 10;
}
//figures-->从右往左数第figures位的数字  0  ==》 个位
void Radix(int* arr, int len, int figures)
{
	//初始化链表,分别表示基数0 1 2 3 4 5 6 7 8 9
	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代表桶的个数,下标
	{
		if (DeleteFirst(&head[j], &arr[i]))
		{
			i++;
		}
		else
		{
			j++;
		}
	}
}
//O(d*n)
void RadixSort(int* arr, int len)
{
	//得到最大数的位数,来确定桶的数量
	int count = GetMaxBit(arr, len);
	for (int i = 0; i < count; i++)//d
	{
		Radix(arr, len, i);//n
	}
}

void Show(int* arr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 12,88,3333,999,4343,223,212,7676,343 };
	int len = sizeof(arr) / sizeof(arr[0]);
	RadixSort(arr, len);
	Show(arr, len);
	return 0;
}

9.时间复杂度、空间复杂度、稳定性总结

在这里插入图片描述

发布了124 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42214953/article/details/105095654