排序算法总结

经典排序算法详解

1、概述

排序算法包括内排序和外排序,所谓的内排序就是要排序的数据全部在内存中,而外排序是因为要排序的数据太
大,一次性无法全部读入到内存中,我们要介绍的排序算法主要是内排序。在内排序算法中,如果在排序的最终结果中,各元素的次序依赖于它们之间的比较,那么我们称此类排序算法为比较排序。各排序的层次结构图如下所示:

2、比较排序

1、交换排序类

1、冒泡排序

基本思想:用第一个元素依次和后面的元素相比较,如果前者比后者大那么就交换二者,交换完成后

最后一个元素就是最大的元素,循环下去直到完成排序。冒泡排序在最好的情况下(元素是按照从小到大的顺序存储的)运行的时间为O(n),最坏和平均时间复杂度为 O(n^2)。代码如下:

void buble_sort(int array[],int n)
{
	int i, j,temp;
	bool didswap;
	for (i = n; i > 0;i--)
	{
		didswap = false;
		for (j = 0; j < i;j++)
		{
			if (array[j] < array[j+1])
			{
				temp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = temp;
				didswap = true;
			}
		}
		if (didswap == false)
			return;
	}
}

2、快速排序

基本思想:从数组a[p,....,r]中选取一个元素a[q],使得a[p,..,q-1]中的元素都不大于a[q],a[q+1,..,r]中的
元素都大于 a[q],然后对a[p,..,q-1]、a[q+1,..,r]进行递归排序,求取a[q]元素的过程如下所示。快速排序的最好和平均运行时间复杂度为O(n*logn),最坏情况下(每次分解数组的时候都是一个空数组和一个n-1长度的数组)的时间复杂度为O(n^2)

int partion(int array[], int start, int end)
{
	int key = array[end];
	int i, j;
	int temp;

	for (i = start,j = start; i < end;i++)
	{
		if (array[i] < key)
		{
			temp = array[i];
			array[i] = array[j];
			array[j] = temp;
			j++;
		}
	}
	temp = array[end];
	array[end] = array[j];
	array[j] = temp;
	return j;
}
void quick_sort(int array[], int start, int end)
{
	if (start < end)
	{
		int q = partion(array, start, end);
		quick_sort(array, start, q - 1);
		quick_sort(array, q + 1, end);
	}	
}


2、插入排序类

1、直接插入排序

基本思想:将待排序的元素插入到已经排好序的子数组中,当所有元素插入完毕后,则该素组就排好
序,排序步骤如下。直接插入排序的最坏时间复杂度为O(n^2),最好情况为 O(n),平均情况为O(n^2)。

void insert_sort(int array[], int n)
{
	int key;
	int i,j;

	for (i = 1; i < n;i++)
	{	
		key = array[i];
		j = i - 1;
		while (j >= 0 && array[j] > key)
		{
			array[j + 1] = array[j];
			j--;
		}
		array[j + 1] = key;
	}
}


2、希尔排序

希尔排序(Shell Sort),也称递减增量排序算法,是插入排序的一种更高效的改进版本。该算法的基本

思想是:把数组下标按照增量进行分组,对每组元素进行直接插入排序;随着增量的减小每组的元素个数不断增加,直到增量减为1为止,算法结束。希尔排序的最坏时间复杂度为O(n^2),最好情况为O(n^1.3),平均情况为O(n*logn)~O(n^2)。算法过程如下图所示:

void shell_insert_sort(int array[], int n)
{
	int gap, i, j, k,key;
	/*对数组进行分组*/
	for (gap = n / 2; gap > 0;gap/=2)
	{
		/*对每组元素进行直接插入排序*/
		for (i = gap; i < n;i+=gap)
		{
			j = i - gap;
			key = array[i];
			while (j>=0 && array[j] > key)
			{
				array[j + gap] = array[j];
				j -= gap;
			}
			array[j + gap] = key;
		}
	}
}



3、选择排序类

1、选择排序

基本思想:每次从待排序的序列中选出最小(或最大)的元素放在序列的起始位置,直到所有待排序的序

列排完。选择排序的最好和最坏情况下的时间复杂度均为O(n^2)。

void select_sort(int array[], int n)
{
	int i, j, low,temp;
	for (i = 0; i < n;i++)
	{
		low = i;
		/*找出序列中的最小元素的下标*/
		for (j = i; j < n;j++)
		{
			if (array[low] > array[j])
				low = j;
		}
		/*用序列中的最小元素与起始元素交换*/
		temp = array[low];
		array[low] = array[i];
		array[i] = temp;
	}
}


2、堆排序

堆是一个数组,他可以被看成是一个近似的完全二叉树,树的根节点是A[0],这样给定一个节点i我们很容 

易计算出其左孩子和右孩子的下标:left = 2*i + 1,right = 2*i + 2.。二叉堆又分为最大堆和最小堆,节点的值都要满足堆的性质,在最大堆中节点满足A[i] >= A[left]、A[i] >= A[right],在最小堆中节点满足A[i] <= A[left]、A[i] <= A[right]。

最大堆排序的基本思想:给定待排序的数组a首先构造一个最大堆,因此最大堆的根节点即a[0]是堆中最大

的元素,将堆顶元素a[0]和a[n]进行交换,交换之后我们还要维护最大堆的性质,此时堆中的有效元素个数为n-1,然后再将a[0]和a[n-1]进行交换,依次进行下去直到堆中只有一个有效元素为止。堆排序的最好情况和最坏情况下的时间复杂度都为O(n*logn)。如何构造一个最大堆?我们采用自底向上的方式创建一个最大堆,下图给出了解释


void keep_max_heap(int array[], int n,int i)
{
	/*计算数组下标为i的左孩子的下标和右孩子的下标*/
	int left = 2*i + 1;
	int right = 2*i + 2;

	int temp;
	int largst;
	/*找出三者之间最大数的下表*/
	if (left < n && array[i] < array[left])
		largst = left;
	else
		largst = i;
	if (right < n && array[right] > array[largst])
		largst = right;
	if (largst != i)
	{
		temp = array[largst];
		array[largst] = array[i];
		array[i] = temp;
		keep_max_heap(array, n, largst);
	}
}

void heap_sort(int array[], int n)
{
	/*先创建一个最大堆*/
	int start = n / 2 - 1;
	for (int i = start; i >= 0; i--)
		keep_max_heap(array, n, i);
	/*从最大堆中取出array[0]与array[n-1]进行交换*/
	int temp;
	for (int j = n - 1; j >= 0;j--)
	{
		temp = array[0];
		array[0] = array[j];
		array[j] = temp;
		keep_max_heap(array, j, 0);
	}
}

4、归并排序

归并排序是建立在归并操作上的一种有效算法,是分治策略的一种典型应用。基本思想:将待排序的数组
分解成两个子数组,对每个子数组进行排序,然后将排序好的子数组进行归并操作。归并排序的最好和最坏情况下的时间复杂度都为O(n*logn)。所谓的归并操作是对于已排序好的数组a和b,选取数组a、b中的第一个元素进行比较,如果a中的元素大于b中的元素,那么就把b中的此元素赋值给数组c,并将b中的元素删除,否则 就把a中的元素赋值给数组c,并将a中的此元素删除,循环下去直到其中一个数组为空,然后将另一个数组的所有元素依次赋值给数组c。归并操作的图解如下:

void merge(int a[], int start, int mid, int end)
{
	int i = start, j = mid + 1;
	int k = 0;

	int n = mid - start + 1;
	int m = end - mid;

	int *c = (int *)malloc(sizeof(int)* (m + n));

	while (i <= mid && j <= end)
	{
		if (a[i] < a[j])
		{
			c[k] = a[i];
			i++;
		}
		else
		{
			c[k] = a[j];
			j++;
		}
		k++;
	}
	if (i > mid)
	{
		while (j <= end)
		{
			c[k] = a[j];
			j++;
			k++;
		}
	}
	else
	{
		while (i <= mid)
		{
			c[k] = a[i];
			i++;
			k++;
		}
	}
	/*将排好序的数组C中的元素复制到数组a中*/
	for (int r = 0; r < (m + n);r++)
		a[r + start] = c[r];
	free(c);
}

void merge_sort(int array[], int start,int end)
{
	if (end <= start)
		return;

	int mid = (start + end) / 2;
	merge_sort(array, start, mid);
	merge_sort(array, mid + 1, end);
	merge(array, start, mid, end);
}

3、线性时间排序

1、计数排序

计数排序对待排序的序列有要求:序列中的元素个数为n,序列中的元素取值范围为0~k,必须满足k<n,因
为对 输入序列做了假设,因此计数排序可以在O(n)时间内完成。基本思想:统计序列a中元素值为i出现的次数,记录在数组c[i]中,计算出所有比i元素大的元素个数m,那么i元素则放在数组b的第m个位置。因为正统的基数排序算法需要一个额外的数组b,这样浪费了内存,因此我们队算法进行改进:
void count_sort(int array[], int n)
{
	int *c = (int *)malloc(sizeof(int)* n);

	int i;

	for (i = 0; i < n; i++)
		c[i] = 0;
	/*统计array[i]的元素个数*/
	for (i = 0; i < n;i++)
		c[array[i]] = c[array[i]] + 1;

	int z = 0;
	for (i = 0; i < n;i++)
	{
		while (c[i]-- > 0)
			array[z++] = i;		
	}
}

2、基数排序

基数排序属于“分配式”排序,又称“桶子法”,顾名思义它根据键值的部分资讯将元素分配的某个“桶”中,籍
以达到 排序的目的。对于我们使用的阿拉伯数字,其基数是0~9,因此我们可以用十个“桶”来存放这些元素。在基数排序的过程中,我们先排个位数,根据个位数的数值分配存放的桶,然后再把桶中的元素收集起来,然后再依次排序十位、百位......,最后完成排序。基数排序的时间复杂度为O(n)。排序过程如下所示:

//辅助链表数据结构
typedef struct node
{
	int data;
	struct node *next;
}LNode,*Link;

void radix_sort(int array[], int n, int m)
{
	int i,j,postion;
	/*从低位到高位排序*/
	for (i = 1; i <= m; i++)
	{
		Link bucket[10];
		/*对链表初始化*/
		for (j = 0; j < 10; j++)
			init_link(&bucket[j]);
		/*对数组中的元素,按照第i位放入桶中,即分配桶*/
		for (j = 0; j < n;j++)
		{
			postion = get_position(array[j], i);
			insert_link(bucket[postion], array[j]);
		}
		/*从桶中收集元素*/
		int k = 0;
		for (j = 0; j < 10;j++)
		{
			if (bucket[j] != NULL)
			{
				Link temp = bucket[j];
				while (!is_empty_link(temp))
				{
					delete_link(temp, &array[k]);
					k++;
				}
			}
		}
	}
}

int get_position(int num, int m)
{
	for (; m > 1; m--)
		num /= 10;
	return(num % 10);
}

void init_link(Link *link)
{
	*link = (Link)malloc(sizeof(LNode));
	(*link)->next = NULL;
}

bool is_empty_link(Link link)
{
	if (link->next != NULL)
		return false;
	return true;
}

void insert_link(Link link, int e)
{
	Link temp = (Link)malloc(sizeof(LNode));
	temp->data = e;
	temp->next = NULL;

	Link x = link->next;
	Link y = x;
	while (x != NULL)
	{
		y = x;
		x = x->next;
	}
	if (y == NULL)
		link->next = temp;
	else
		y->next = temp;
}

bool delete_link(Link link, int *e)
{
	if (is_empty_link(link))
		return false;

	Link temp = link->next;

	link->next = temp->next;
	*e = temp->data;
	free(temp);
	return true;
}

3、桶排序

桶排序假设输入的数据服从均匀分布,并独立地分布在[0,1)区间,由于对输入数据做了假设,因此桶排序

的速度也很快,平均情况下它的时间代价为O(n)。基本思想:将 [0,1)区间划分为n个相同大小的子区间,然后将n个输入分别放到各个桶中,并对每个桶进行排序,遍历每个桶依次把数据列出来即可。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值