排序算法

突然发现自己对排序算法陌生的很, 趁这几天有空, 翻了网上的资料, 自己写了一遍排序算法, 把自己写的记录下来, 供以后回顾.

#include <iostream>
#include <time.h>

using namespace std;

// 通用输出函数
void PrintArray(int a[], int n, char* prefix = "")
{
	printf("\n==========%s==========\n", prefix);
	for (int j = 0; j < n; ++j)
	{
		j != 0 && j % 20 == 0 && cout << endl;
		printf("%4d", a[j]);
	}
	cout << endl;
}

/*
	插入排序: 
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表
	最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
	最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
	平均时间复杂度 ---- O(n^2)
	所需辅助空间 ------ O(1)
	稳定性 ------------ 稳定
*/
void InsertSort(int a[], int n)
{
	for (int i = 1; i < n; ++i)
	{
		//若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入 
		if (a[i] < a[i - 1])
		{
			int j = i - 1;
			// 保存要插入的值(向前移动的时候会覆盖掉)
			int v = a[i];
			while (j >=0 && a[j] > v)
			{
				// 向前移动
				a[j + 1] = a[j];
				--j;
			}
			//插入到正确位置
			a[j+1] = v;
		}
	}
}

void ShellInsertSort(int a[], int n, int dk)
{
	//排序间距为dk的值, 原理跟插入排序一样
	for (int i = dk; i < n; ++i)
	{
		if (a[i] < a[i - dk])
		{
			int j = i - dk;
			int v = a[i];
			while (j >= 0 && a[j] > v)
			{
				a[j + dk] = a[j];
				j -= dk;
			}
			a[j + dk] = v;
		}
	}
}

/*
	希尔排序 :
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,
待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序
	最差时间复杂度 ---- 根据步长序列的不同而不同。已知最好的为O(n(logn)^2)
	最优时间复杂度 ---- O(n)
	平均时间复杂度 ---- 根据步长序列的不同而不同。
	所需辅助空间 ------ O(1)
	稳定性 ------------ 不稳定
*/
void ShellSort(int a[], int n)
{
	int dk = n / 2;
	while (dk >= 1)
	{
		ShellInsertSort(a, n, dk);
		dk /= 2;
	}
}

/*
	简单选择排序: 
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;
然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推
	最差时间复杂度 ---- O(n^2)
	最优时间复杂度 ---- O(n^2)
	平均时间复杂度 ---- O(n^2)
	所需辅助空间 ------ O(1)
	稳定性 ------------ 不稳定
*/
void ChooseSort(int a[], int n)
{
	for (int i = 0; i < n; ++i)
	{
		int minIdx = i;
		for (int j = i + 1; j < n; ++j)
		{
			// 选出一个最小的值
			if (a[j] < a[minIdx])
			{
				minIdx = j;
			}
		}
		// 移动到最小的值到正确的位置
		if (minIdx != i)
			swap(a[i], a[minIdx]);
	}
}

void Heapify(int a[], int n, int i)
{
	//从i向下进行堆调整
	int lChild = 2 * i + 1;
	int rChild = lChild + 1;

	if (lChild >= n)
		return;

	// 选出当前结点与其左右孩子三者之中的最大值
	int maxIdx = i;
	if (a[lChild] > a[maxIdx])
		maxIdx = lChild;
	if (rChild < n && a[rChild] > a[maxIdx])
		maxIdx = rChild;

	if (maxIdx != i)
	{
		// 把当前结点和它的最大(直接)子节点进行交换
		swap(a[maxIdx], a[i]);
		// 交换后, 交换孩子节点需要重新调整
		Heapify(a, n, maxIdx);
	}
}

void BuildHeap(int a[], int n)
{
	// 建堆,时间复杂度O(n)
	for (int i = (n-1)/2; i >= 0; --i)
	{
		// 从每一个非叶结点开始向下进行堆调整
		Heapify(a, n, i);
	}
}

/*
	堆排序: 
堆排序是指利用堆这种数据结构所设计的一种选择排序算法
	最差时间复杂度 ---- O(nlogn)
	最优时间复杂度 ---- O(nlogn)
	平均时间复杂度 ---- O(nlogn)
	所需辅助空间 ------ O(1)
	稳定性 ------------ 不稳定
*/
void HeapSort(int a[], int n)
{
	BuildHeap(a, n);

	for (int i = n-1; i > 0; --i)
	{
		// 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素
		swap(a[0], a[i]);
		// 从新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)
		Heapify(a, i, 0);
	}
}

/*
	冒泡排序:
依次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,
直到没有元素再需要交换,排序完成
	最差时间复杂度 ---- O(n^2)
	最优时间复杂度 ---- O(n^2)
	平均时间复杂度 ---- O(n^2)
	所需辅助空间 ------ O(1)
	稳定性 ------------ 稳定
*/
void BubbleSort(int a[], int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		for (int j = 0; j < n - i - 1; ++j)
		{
			// 依次比较相邻的两个元素,使较小的那个向后移
			if (a[j + 1] < a[j])
			{
				swap(a[j + 1], a[j]);
			}
		}
	}
}

/*
	冒泡排序算法的改进 :
加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,
如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,
可立即结束排序,避免不必要的比较过程
	最差时间复杂度 ---- O(n^2)
	最优时间复杂度 ---- O(n)
	平均时间复杂度 ---- O(n^2)
	所需辅助空间 ------ O(1)
	稳定性 ------------ 稳定
*/
void BubbleSort_1(int a[], int n)
{
	for (int i = 0; i < n - 1;)
	{
		int exchangeIdx = 0;
		for (int j = 0; j < n - i - 1; ++j)
		{
			if (a[j + 1] < a[j])
			{
				exchangeIdx = j;
				swap(a[j + 1], a[j]);
			}
		}
		i = n - exchangeIdx -1;
	}
}

int Partition(int a[], int left, int right)
{
	int pivotIdx = left;
	while (left < right)
	{
		// 顺序很重要,要先从右边开始找(从左找最后一个交换的时候可能会有问题)
		while (a[right] >= a[pivotIdx] && left < right)
			--right;
		while (a[left] <= a[pivotIdx] && left < right)
			++left;
		if (left < right)
			swap(a[left], a[right]);
	}
	swap(a[left], a[pivotIdx]);
	return left;
}

/*
	快速排序 :
  1.选择一个基准元素,通常选择第一个元素或者最后一个元素,
  2.把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),
这个称为分区(Partition)操作。
  4.此时基准元素在其排好序后的正确位置
  3.然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序
)
	最差时间复杂度 ---- 每次选取的基准都是最大(或最小)的元素,
导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
	最优时间复杂度 ---- 每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,
只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
	平均时间复杂度 ---- O(nlogn)
	所需辅助空间 ------ 主要是递归造成的栈空间的使用(用来保存left和right等局部变量),
取决于递归树的深度,一般为O(logn),最差为O(n)
	稳定性 ---------- 不稳定
*/
void QuickSort(int a[], int left, int right)
{
	if (left < right)
	{
		int pivotIdx = Partition(a, left, right);
		QuickSort(a, left, pivotIdx - 1);
		QuickSort(a, pivotIdx + 1, right);
	}
}

// 快速排序法另一种写法
void Quicksort_1(int a[], int n)
{
	if (n <= 1)
		return;

	//swap(a[0], a[rand() % n]); // move pivot elem to v[0]
	int last = 0;
	for (int i = 1; i < n; i++)
		if (a[i] < a[0])
			swap(a[++last], a[i]);
	swap(a[0], a[last]);

	Quicksort_1(a, last);
	Quicksort_1(a + last + 1, n - last - 1);
}

void Merge(int a[], int left, int mid, int right)
{
	int len = right - left + 1;
	int p1 = left;		// 前一数组的起始元素
	int p2 = mid + 1;	// 后一数组的起始元素
	int arrayIndex = 0;
	int* tempArray = new int[len];	// 辅助空间O(n)

	while (p1 <= mid && p2 <= right)
		// 带等号保证归并排序的稳定性
		tempArray[arrayIndex++] = a[p1] <= a[p2] ? a[p1++] : a[p2++];
	while (p1 <= mid)
		// 保证所有数据写入
		tempArray[arrayIndex++] = a[p1++];
	while (p2 <= right)
		// 保证所有数据写入
		tempArray[arrayIndex++] = a[p2++];
	for (int i = 0; i < len; ++i)
		a[left++] = tempArray[i];

	delete tempArray;
	tempArray = nullptr;
}

/*
归并排序:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾
最差时间复杂度 ---- O(nlogn)
最优时间复杂度 ---- O(nlogn)
平均时间复杂度 ---- O(nlogn)
所需辅助空间 ------ O(n)
稳定性 ------------ 稳定
*/
void MergeSortRecursion(int a[], int left, int right)
{
	// 递归实现的归并排序(自顶向下)
	// 当待排序的序列长度为1时,递归开始回溯,进行merge操作
	if (left == right)
		return;

	int mid = (left + right) / 2;
	MergeSortRecursion(a, left, mid);
	MergeSortRecursion(a, mid + 1, right);
	Merge(a, left, mid, right);
}

void MergeSortIteration(int a[], int n)
{
	// 非递归(迭代)实现的归并排序(自底向上)
	int left, mid, right;
	for (int i = 1; i < n; i *= 2)
	{
		left = 0;
		// 后一个子数组存在(需要归并)
		while (left + i < n)
		{
			mid = left + i - 1;
			right = mid + i < n ? mid + i : n - 1;
			Merge(a, left, mid, right);
			left = right + 1;
		}
	}
}

int getdigit(int x, int d)
{
	int key = pow(10, d-1);
	return (x / key) % 10;
}

// 基数排序(桶排序)
void LSDRadixSort(int arr[], int begin, int end, int d)
{
	const int radix = 10;
	int count[radix], i, j;

	int *bucket = (int*)malloc((end - begin + 1)*sizeof(int));  //所有桶的空间开辟   

	//按照分配标准依次进行排序过程
	for (int k = 1; k <= d; ++k)
	{
		//置空
		for (i = 0; i < radix; i++)
		{
			count[i] = 0;
		}
		//统计各个桶中所盛数据个数
		for (i = begin; i <= end; i++)
		{
			count[getdigit(arr[i], k)]++;
		}
		//count[i]表示第i个桶的右边界索引
		for (i = 1; i < radix; i++)
		{
			count[i] = count[i] + count[i - 1];
		}
		//把数据依次装入桶(注意装入时候的分配技巧)
		for (i = end; i >= begin; --i)        //这里要从右向左扫描,保证排序稳定性   
		{
			j = getdigit(arr[i], k);        //求出关键码的第k位的数字, 例如:576的第3位是5   
			bucket[count[j] - 1] = arr[i]; //放入对应的桶中,count[j]-1是第j个桶的右边界索引 
			--count[j];               //对应桶的装入数据索引减一  
		}

		//注意:此时count[i]为第i个桶左边界  

		//从各个桶中收集数据
		for (i = begin, j = 0; i <= end; ++i, ++j)
		{
			arr[i] = bucket[j];
		}
	}
	free(bucket);
}


// 将数组打乱
void RandomArray(int a[], int n)
{
	static unsigned int tAdd = time(nullptr);
	char* p = reinterpret_cast<char*>(&tAdd);
	tAdd += 1024;
	swap(p[0], p[2]);
	swap(p[1], p[3]);

	srand(tAdd);
	for (int i = 0; i < n; ++i)
	{
		int randIdx = rand() % n;
		swap(a[i], a[randIdx]);
	}
}

#define _COUNT_ 19

void main()
{
	// 创造一个有序数组
	int a[_COUNT_];
	for (int i = 0; i < _COUNT_; ++i)
	{
		a[i] = i + 1;
	}	

	// 插入排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");	
	InsertSort(a, _COUNT_);		
	PrintArray(a, _COUNT_, "InsertSort");

	// 希尔排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	ShellSort(a, _COUNT_);
	PrintArray(a, _COUNT_, "ShellSort");

	// 简单选择排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	ChooseSort(a, _COUNT_);
	PrintArray(a, _COUNT_, "ChooseSort");

	// 堆排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	HeapSort(a, _COUNT_);
	PrintArray(a, _COUNT_, "HeapSort");

	// 冒泡排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	BubbleSort(a, _COUNT_);
	PrintArray(a, _COUNT_, "BubbleSort");

	// 冒泡排序算法的改进
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	BubbleSort_1(a, _COUNT_);
	PrintArray(a, _COUNT_, "BubbleSort_1");

	// 快速排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	QuickSort(a, 0, _COUNT_ - 1);
	PrintArray(a, _COUNT_, "QuickSort");

	// 快速排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	Quicksort_1(a, _COUNT_);
	PrintArray(a, _COUNT_, "Quicksort_1");

	// 归并排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	MergeSortRecursion(a, 0, _COUNT_-1);
	PrintArray(a, _COUNT_, "MergeSortRecursion");

	// 归并排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	MergeSortIteration(a, _COUNT_);
	PrintArray(a, _COUNT_, "MergeSortIteration");

	// 基数排序
	RandomArray(a, _COUNT_);
	PrintArray(a, _COUNT_, "begin sort");
	LSDRadixSort(a, 0, _COUNT_ - 1, log10(_COUNT_)+1);
	PrintArray(a, _COUNT_, "LSDRadixSort");

	system("pause");
}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值