七种排序算法

3 篇文章 0 订阅

本文涉及的七种排序算法有:冒泡排序、选择排序、插入排序、希尔排序、堆排序、归并排序、快速排序。


1、冒泡排序


从最后一位开始,相邻的数两两比较,小的换到前面,这样每一趟下来,最小的一个数就像气泡一样从水下漂浮上来。

//冒泡排序
void Sort_Bubble(int *p)
{
	int i, j;
	int temp;

	for (i = 0; i < MAX_SIZE - 1; i++)
	{
		for (j = MAX_SIZE - 1; j > i; j--)
		{
			if (p[j] < p[j-1])
			{
				temp = p[j];
				p[j] = p[j-1];
				p[j-1] = temp;
			}
		}
	}
}


2、选择排序


每次从剩下k个数中搜索到最小值,与当前位置的数交换,也就是不断地选择当前最小值。相比于冒泡排序,交换次数大大减少了。

//选择排序
void Sort_Select(int *p)
{
	int i, j;
	int k, min;
	int temp;

	for (i = 0; i < MAX_SIZE - 1; i++)
	{
		k = i;
		min = p[i];
		for (j = i + 1; j < MAX_SIZE; j++)
		{
			if (p[j] < min)
			{
				k = j;
				min = p[j];
			}
		}
		if (k != i)
		{
			temp = p[i];
			p[i] = p[k];
			p[k] = temp;
		}
	}
}


3、插入排序


第k位前面的k-1个数已经有序排列,将第k位这个数从k-1位开始依次与前面比较,直到找到合适的插入位置。相比于前两种算法,比较次数减少了不少。

//插入排序
void Sort_Insert(int *p)
{
	int i, j;
	int temp;

	for (i = 1; i < MAX_SIZE; i++)
	{
		if (p[i] < p[i-1])
		{
			temp = p[i];
			j = i - 1;
			while (j >= 0 && p[j] > temp)
			{
				p[j+1] = p[j];
				j--;
			}
			p[j+1] = temp;
		}
	}
}

4、希尔排序


希尔排序有点类似于插入排序,不过它不像插入排序那样每次一位一位地与前面比较,而是一开始大跨度地与前面比较和交换。这个跨度逐次递减,直到最终减为1。据说,跨度按照2^k-1的规律递减,效果会比较好,也就是……31、15、7、3、1。

//希尔排序
void Sort_Shell(int *p)
{
	int i, j;
	int k, temp;

	k = 1;
	while (k < MAX_SIZE)
	{
		k *= 2;
	}
	k = k/2 - 1; //确定初始的间隔k=2^n-1

	while (k > 0) 
	{
		for (i = k; i < MAX_SIZE; i++)
		{
			if (p[i] < p[i-k])
			{
				temp = p[i];
				for (j = i-k; j >= 0 && p[j] > temp; j -= k)
				{
					p[j+k] = p[j];
				}
				p[j+k] = temp;
			}
		}
		k = (k+1)/2 - 1; //间隔按2^n-1规律递减到1
	}
}

5、堆排序


堆是一种特殊的完全二叉树。如果每个节点的值都不小于它的左右孩子的值,那么它是大顶堆;反之,如果每个节点的值都不大于它的左右孩子的值,那么它是小顶堆。这里用到的是大顶堆。

另外,完全二叉树还有一条性质在这里非常有用:如果根节点编号从1开始,那么编号为k的节点,左孩子(如果有的话)编号是2k,右孩子(如果有的话)编号为2k+1。

堆排序的基本原理是,把乱序数列看作一棵完全二叉树,先调整为大顶堆,此时根节点就是最大数,交换根节点与最后一个节点,再把之前n-1个节点调整为大顶堆,重复这个过程。

堆排序有点类似于选择排序,不过在每次选择出最大值的同时,也把中间数字的顺序整理了一下,这样以后的调整会快很多。

//堆排序
void Sort_Heap(int *p)
{
	int i;
	int temp;

	//编号为MAX_SIZE/2之前的节点都不是叶子,从下向上调整
	for (i = MAX_SIZE/2; i > 0; i--) 
	{
		BigTopHeap(p, i, MAX_SIZE);
	}

	for (i = MAX_SIZE; i > 1; i--)
	{
		temp = p[i-1];
		p[i-1] = p[0];
		p[0] = temp;
		BigTopHeap(p, 1, i-1);
	}
}

//构造大顶堆,树根从1开始编号
//所以,数组编号 = 树节点编号 - 1
void BigTopHeap(int *p, int root, int last)
{
	int k;
	int temp;

	temp = p[root-1];
	for (k = 2*root; k <= last; k*=2)
	{
		if (k < last && p[k-1] < p[k]) //左小于右,则转到右
		{
			k++;
		}
		if (p[k-1] <= temp) //后面没有大于temp的,则退出循环
		{
			break;
		}
		p[root-1] = p[k-1];	//大数移到上一层
		root = k;			//当前作为树根
	}
	p[root-1] = temp;
}

6、归并排序


归并排序的基本原理就是把数列不断地拆分成小段,直到一个个数字独立构成一段。然后再不断地把这些小段在合并中排序,最终形成一个有序数列。如下图所示。


//归并排序
void Sort_Merge(int *p)
{
	int i;
	int q[MAX_SIZE];

	i = 1;
	while (i < MAX_SIZE)
	{
		MergeProcess(q, p, i, MAX_SIZE);
		i *= 2;
		MergeProcess(p, q, i, MAX_SIZE);
		i *=2;
	}
}

//归并的过程,将相邻两段归并为一段
//数组p分段有序,间隔为i,总长为n,归并到数组q
void MergeProcess(int *q, int *p, int i, int n)
{
	int j;

	j = 0;
	while (j + 2*i < n) //两两合并前面的段,剩下不超过完整的两段
	{
		Merge(q, p, j, j+i, j+2*i-1);
		j += 2*i;
	}
	if (j + i < n) //剩下两段,其中一段长度完整,一段不完整,也合并它们
	{
		Merge(q, p, j, j+i, n-1);
	} 
	else //只剩下一段,直接搬下来
	{
		while (j < n)
		{
			q[j] = p[j];
			j++;
		}
	}
}

//合二为一:数组p分两段,各自有序,合一为数组q
//left是第1段起始下标,mid是第2段起始下标,end是结束下标
void Merge(int *q, int *p, int first, int second, int end)
{
	int i, j, k;

	i = first;
	j = second;
	k = first;

	//分别比较两段的最左端数字
	while (i < second && j <= end)
	{
		if (p[i] < p[j])
		{
			q[k] = p[i];
			i++;
		} 
		else
		{
			q[k] = p[j];
			j++;
		}
		k++;
	}

	//把有剩余数字的一段复制到末尾
	if (i < second)
	{
		while (i < second)
		{
			q[k] = p[i];
			k++;
			i++;
		}
	}
	if (j <= end)
	{
		while (j <= end)
		{
			q[k] = p[j];
			k++;
			j++;
		}
	}
}

7、快速排序


快速排序的基本原理是,在数列中找到一个数k和一个位置p,使得第一趟比较和交换之后,位置p左边的数都小于等于k,位置p右边的数都大于等于k,之后对左右两边分别重复该过程即可。

//快速排序
void Sort_Quick(int *p)
{
	Quick(p, 0, MAX_SIZE - 1);
}

//快速排序递归函数
void Quick(int *p, int left, int right)
{
	int pos;

	if (left < right)
	{
		pos = Partition(p, left, right);	//获得分割点
		Quick(p, left, pos - 1);			//对左半部分快速排序
		Quick(p, pos + 1, right);			//对右半部分快速排序
	}
}

//分割数组,并获得分割点位置
int Partition(int *p, int left, int right)
{
	int key, temp;

	key = p[left];
	while (left < right)
	{
		while (left < right && p[right] >= key)
		{
			right--;
		}
		temp = p[left];
		p[left] = p[right];
		p[right] = temp;
		
		while (left < right && p[left] <= key)
		{
			left++;
		}
		temp = p[left];
		p[left] = p[right];
		p[right] = temp;
	}

	return left;
}

8、运行结果


取0到9999的一个随机排列,共10000个数,分别用以上七种算法进行排序,验证排序是否正确,并且得到算法消耗的时间。如下图所示。



9、时间性能统计


冒泡排序、选择排序、插入排序这三种属于简单排序算法,它们的时间复杂度均为O(n^2)。希尔排序、堆排序、归并排序、快速排序这四种属于高级排序算法,希尔排序的时间复杂度为O(n^(3/2)),其他三种算法的时间复杂度为O(nlogn)。

通过试验统计各种排序算法所消耗的时间,结果与上面的理论吻合。










10、全部代码


需要解释以下几点:
(1)构造初始的乱序数列需要随机生成从0到MAX_SIZE-1的一个排列,采用的方法是,先按顺序初始化,然后从第1位开始,每次随机产生一个位置,交换当前位置与这个随机位置上的数字。
(2)由于排序算法的返回值、参数都相同,因此采用了回调函数的形式。程序每次执行都会依次调用这七种算法,为了比较时间性能,它们要对同样的初始数列进行排序。因此,初始生成的随机序列会备份,每次先拷贝,再排序,这样下一个算法还能用同样的初始数列。
(3)高级排序算法效率明显高很多,因此测试它们的消耗时间,要重复排序若干次,然后扣除复制数组的时间,计算每次的平均时间。
#include <STDIO.H>
#include <STDLIB.H>
#include <TIME.H>

#define MAX_SIZE 10000	//数字个数
#define EXE_TIME 100	//试验重复次数

void RandomArrange(int *);
void SimpleSort(int *, void (*)(int *), const char *);
void HighSort(int *, void (*)(int *), const char *, int);
void CopyArray(int *, int *);
void ShowResult(int *, const char *, double);
bool IsSortCorrect(int *);
void Sort_Bubble(int *);
void Sort_Select(int *);
void Sort_Insert(int *);
void Sort_Shell(int *);
void Sort_Heap(int *);
void BigTopHeap(int *, int, int);
void Sort_Merge(int *);
void MergeProcess(int *, int *, int, int);
void Merge(int *, int *, int, int, int);
void Sort_Quick(int *);
void Quick(int *, int, int);
int Partition(int *, int, int);

int main(void)
{
	int num[MAX_SIZE];

	//生成随机数
	RandomArrange(num);

	//简单排序
	SimpleSort(num, Sort_Bubble, "冒泡排序");
	SimpleSort(num, Sort_Select, "选择排序");
	SimpleSort(num, Sort_Insert, "插入排序");

	//高级排序
	HighSort(num, Sort_Shell, "希尔排序", EXE_TIME);
	HighSort(num, Sort_Heap, "堆排序", EXE_TIME);
	HighSort(num, Sort_Merge, "归并排序", EXE_TIME);
	HighSort(num, Sort_Quick, "快速排序", EXE_TIME);

	return 0;
}

//生成0到MAX-1的随机排列
void RandomArrange(int *p)
{
	int i, j;
	int temp;

	srand(time(0));
	for (i = 0; i < MAX_SIZE; i++)
	{
		p[i] = i;
	}
	for (i = 0; i < MAX_SIZE; i++)
	{
		j = rand() % MAX_SIZE;
		temp = p[i];
		p[i] = p[j];
		p[j] = temp;
	}
	printf("已经生成包含%d个数字的随机序列。\n\n", MAX_SIZE);
}

//调用低级排序方法
//p是数组指针,pf是排序函数的指针,q是排序方法字符串
void SimpleSort(int *p, void (*pf)(int *), const char *q)
{
	int a[MAX_SIZE];
	clock_t start, end;
	double sortTime;

	printf("%s正在进行...\n", q);
	start = clock();
	CopyArray(a, p);
	pf(a);
	end = clock();
	sortTime = (double)(end - start);
	ShowResult(a, q, sortTime);
}

//调用高级排序方法
//p是数组指针,pf是排序函数的指针,q是排序方法字符串,n是重复试验次数
void HighSort(int *p, void (*pf)(int *), const char *q, int n)
{
	int i;
	int a[MAX_SIZE];
	clock_t start, end, copyTime, totolTime;
	double sortTime;

	i = 0;
	start = clock();
	while (i < n)
	{
		CopyArray(a, p);
		i++;
	}
	end = clock();
	copyTime = end - start; //复制数组的时间

	printf("%s正在进行...\n", q);
	i = 0;
	start = clock();
	while (i < n)
	{
		CopyArray(a, p);
		pf(a);
		i++;
	}
	end = clock();
	totolTime = end - start; //总时间
	
	sortTime = (double)(totolTime - copyTime)/n; //实际排序时间
	ShowResult(a, q, sortTime);
}

//把数组q复制到数组p
void CopyArray(int *p, int *q)
{
	int i;

	for (i = 0; i < MAX_SIZE; i++)
	{
		p[i] = q[i];
	}
}

//输出结论
void ShowResult(int *p, const char *q, double t)
{
	printf("%s", q);
	if (IsSortCorrect(p))
	{
		printf("已经完成,耗时 %g 毫秒。\n\n", t);
	} 
	else
	{
		printf("出现错误。\n\n");
	}
}

//检查排序结果是否正确
bool IsSortCorrect(int *p)
{
	int i = 0;
	while (i < MAX_SIZE && p[i] == i)
	{
		i++;
	}
	if (i == MAX_SIZE)
	{
		return true;
	} 
	else
	{
		return false;
	}
}

//冒泡排序
void Sort_Bubble(int *p)
{
	int i, j;
	int temp;

	for (i = 0; i < MAX_SIZE - 1; i++)
	{
		for (j = MAX_SIZE - 1; j > i; j--)
		{
			if (p[j] < p[j-1])
			{
				temp = p[j];
				p[j] = p[j-1];
				p[j-1] = temp;
			}
		}
	}
}

//选择排序
void Sort_Select(int *p)
{
	int i, j;
	int k, min;
	int temp;

	for (i = 0; i < MAX_SIZE - 1; i++)
	{
		k = i;
		min = p[i];
		for (j = i + 1; j < MAX_SIZE; j++)
		{
			if (p[j] < min)
			{
				k = j;
				min = p[j];
			}
		}
		if (k != i)
		{
			temp = p[i];
			p[i] = p[k];
			p[k] = temp;
		}
	}
}

//插入排序
void Sort_Insert(int *p)
{
	int i, j;
	int temp;

	for (i = 1; i < MAX_SIZE; i++)
	{
		if (p[i] < p[i-1])
		{
			temp = p[i];
			j = i - 1;
			while (j >= 0 && p[j] > temp)
			{
				p[j+1] = p[j];
				j--;
			}
			p[j+1] = temp;
		}
	}
}

//希尔排序
void Sort_Shell(int *p)
{
	int i, j;
	int k, temp;

	k = 1;
	while (k < MAX_SIZE)
	{
		k *= 2;
	}
	k = k/2 - 1; //确定初始的间隔k=2^n-1

	while (k > 0) 
	{
		for (i = k; i < MAX_SIZE; i++)
		{
			if (p[i] < p[i-k])
			{
				temp = p[i];
				for (j = i-k; j >= 0 && p[j] > temp; j -= k)
				{
					p[j+k] = p[j];
				}
				p[j+k] = temp;
			}
		}
		k = (k+1)/2 - 1; //间隔按2^n-1规律递减到1
	}
}

//堆排序
void Sort_Heap(int *p)
{
	int i;
	int temp;

	//编号为MAX_SIZE/2之前的节点都不是叶子,从下向上调整
	for (i = MAX_SIZE/2; i > 0; i--) 
	{
		BigTopHeap(p, i, MAX_SIZE);
	}

	for (i = MAX_SIZE; i > 1; i--)
	{
		temp = p[i-1];
		p[i-1] = p[0];
		p[0] = temp;
		BigTopHeap(p, 1, i-1);
	}
}

//构造大顶堆,树根从1开始编号
//所以,数组编号 = 树节点编号 - 1
void BigTopHeap(int *p, int root, int last)
{
	int k;
	int temp;

	temp = p[root-1];
	for (k = 2*root; k <= last; k*=2)
	{
		if (k < last && p[k-1] < p[k]) //左小于右,则转到右
		{
			k++;
		}
		if (p[k-1] <= temp) //后面没有大于temp的,则退出循环
		{
			break;
		}
		p[root-1] = p[k-1];	//大数移到上一层
		root = k;			//当前作为树根
	}
	p[root-1] = temp;
}

//归并排序
void Sort_Merge(int *p)
{
	int i;
	int q[MAX_SIZE];

	i = 1;
	while (i < MAX_SIZE)
	{
		MergeProcess(q, p, i, MAX_SIZE);
		i *= 2;
		MergeProcess(p, q, i, MAX_SIZE);
		i *=2;
	}
}

//归并的过程,将相邻两段归并为一段
//数组p分段有序,间隔为i,总长为n,归并到数组q
void MergeProcess(int *q, int *p, int i, int n)
{
	int j;

	j = 0;
	while (j + 2*i < n)
	{
		Merge(q, p, j, j+i, j+2*i-1);
		j += 2*i;
	}
	if (j + i < n)
	{
		Merge(q, p, j, j+i, n-1);
	} 
	else
	{
		while (j < n)
		{
			q[j] = p[j];
			j++;
		}
	}
}

//合二为一:数组p分两段,各自有序,合一为数组q
//left是第1段起始下标,mid是第2段起始下标,end是结束下标
void Merge(int *q, int *p, int first, int second, int end)
{
	int i, j, k;

	i = first;
	j = second;
	k = first;

	//分别比较两段的最左端数字
	while (i < second && j <= end)
	{
		if (p[i] < p[j])
		{
			q[k] = p[i];
			i++;
		} 
		else
		{
			q[k] = p[j];
			j++;
		}
		k++;
	}

	//把有剩余数字的一段复制到末尾
	if (i < second)
	{
		while (i < second)
		{
			q[k] = p[i];
			k++;
			i++;
		}
	}
	if (j <= end)
	{
		while (j <= end)
		{
			q[k] = p[j];
			k++;
			j++;
		}
	}
}

//快速排序
void Sort_Quick(int *p)
{
	Quick(p, 0, MAX_SIZE - 1);
}

//快速排序递归函数
void Quick(int *p, int left, int right)
{
	int pos;

	if (left < right)
	{
		pos = Partition(p, left, right);	//获得分割点
		Quick(p, left, pos - 1);			//对左半部分快速排序
		Quick(p, pos + 1, right);			//对右半部分快速排序
	}
}

//分割数组,并获得分割点位置
int Partition(int *p, int left, int right)
{
	int key, temp;

	key = p[left];
	while (left < right)
	{
		while (left < right && p[right] >= key)
		{
			right--;
		}
		temp = p[left];
		p[left] = p[right];
		p[right] = temp;
		
		while (left < right && p[left] <= key)
		{
			left++;
		}
		temp = p[left];
		p[left] = p[right];
		p[right] = temp;
	}

	return left;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值