数据结构的七种排序

七种排序
从大类上可以分为插入排序、选择排序、交换排序和归并排序
       插入排序: 插入排序、希尔排序
       选择排序:选择排序、堆排序
       交换排序:冒泡排序、快速排序
       归并排序: 归并排序


1、插入排序 
      插入排序通过将一个元素插入一个按照大小排序好的序列这一过程,来实现对所有元素的排序。
      时间复杂度:O(N^2)
插入时的两种情况:
      1)找到了比tmp小的元素下标end,然后将tmp插入end下标的后面
       2)直到end<0时,说明tmp是最小的元素,则将tmp插入第一个位置
代码实现:

//插入排序
template<class T>
void InsertSort(T* a,int n)
{
	for (int i = 0; i < n-1; ++i)
	{
		int end = i;
		int tmp = a[end+1];
		for (end = i; end >= 0; --end)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

2、希尔排序
      对一组数据进行多次分组(gap为每组相邻两元素的间距)预排序,然后再进行一次插入排序。希尔排序是对插入排序的优化。
     原理:是通过预排序将小的元素尽可能较快的移至前面。最后通过 一次插入排序最终整合
     时间复杂度:O(N)~O(N^2)
问:如何确定gap的值?
      刚开始定义gap=n即等于元素的个数,
     单趟预排序通过gap=gap/3+1;来决定每次循环gap的值
     gap>1时进行预排序,gap==1时进行插入排序整合。
注意:预排序不是将每组分开排序,而是将所有组穿插着进行排序
代码实现:

//希尔排序
void ShellSort(int *a, int n)
{
	int gap = n;
	while (gap > 1)  //进行多次预排序
	{
		gap = gap / 3 + 1;
		//gap>1时进行预排序   gap==1时进行最后一次插入排序
		//预排序多组间交替排序(即一次循环可以排多组)
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			for (end = i; end >= 0; end -= gap)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

3、选择排序
      选择排序是每次从某个集合里选出最大或最小的一个元素,每次可以确定一个元素的位置,将集合不断缩小,最终完成对整个集合的排序。
      时间复杂度:O(N^2)
     优化版:每次可以取出最大值和最小值,确定两个位置,提高一倍效率
注意:
       优化版本可能会出现记录最大值的下标(max)在集合最左端(begin),记录最小值的下标(min)在集合最右端(end),出现交换两次后又将两元素交换回去的情况
解决方法:
       当将min下标的元素和begin下标的元素进行交换后,将max变量更新为min,加一条判断即可(详情见代码实现)
代码实现:

//选择排序
//优化版选择排序---每次选择两个(最大和最小)
void SelectSort(int *a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int max = begin;
		int min = begin;
		for (int i = begin; i <= end; ++i)
		{
			if (a[i] > a[max])
			{
				max = i;
			}
			if (a[i] < a[min])
			{
				min = i;
			}
		}
		swap(a[min], a[begin]);
		if (max == begin)   //解决max==begin且min==end造成交换两次的情况
		{
			max = min;
		}
		swap(a[max], a[end]);
		begin++;
		end--;
	}
}

4、堆排序
      通过建堆和堆算法来实现多一个集合的排序
     时间复杂度:O(N*lgN)
堆排序过程:
     1、建大堆
     2、将堆顶元素和堆的最后一个元素交换
    3、堆顶向下调整
    4、将最后一个元素去除在堆外(但不是真正的删除)
代码实现:

void AdjustDown(int *a, int root, int end)   //向下调整
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child <= end)
	{
		if (a[child] < a[child + 1] && (child + 1) <= end)
		{
			++child;
		}
		if (a[parent] >= a[child])
		{
			break;
		}
		swap(a[parent], a[child]);
		parent = child;
		child = parent * 2 + 1;
	}
}
void HeapSort(int *a, int n)
{
	int end = n-1;  //end是数组最后一个元素下标
	//建大堆
	for (int i = (end - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a,i,end);
	}
	//交换元素和调整
	while (end)
	{
		swap(a[0],a[end]);
		--end;    //每次将交换后最大的元素隐藏
		AdjustDown(a,0,end);
	}
}

5、冒泡排序
      通过相邻的元素进行交换排序,每次循环可以确定一个最大或最小的元素,然后缩小集合范围,直至集合有序。
      优化:可以增加一个标志位flag,当序列已经有序时不用再进行多余的比较
      时间复杂度:O(N^2)
代码实现:

//冒泡排序
void BubbleSort(int *a,int n)
{
	for (int i = n-1; i > 0; --i)
	{
		bool flag = 0;  //加标志位提高效率(已经有序时直接退出)
		for (int j = 0; j < i; ++j)
		{
			if (a[j + 1] < a[j])
			{
				swap(a[j + 1], a[j]);
				flag = 1;
			}
		}
		if (flag == 0)  //表示上次一单趟比较没有进行交换--可以直接结束排序
		{
			break;
		}
	}
}

6、快速排序
      找出一个键值key,将集合中小于key的值放一边,大于key的值放在另外一边,即一次排序可以确定key值的准确位置,然后递归排序key的左边和key的右边,直至集合有序。
      时间复杂度:O(N*lgN)
      空间复杂度:O(lgN)
快排的时间复杂度最坏情况的O(N^2),但快排可以通过各种优化保证时间复杂度不会到最坏,所以认为快排的时间复杂度为O(N*lgN)。

快排的单趟排序方法:
       1)左右指针法、挖坑法,两种方法原理基本一致(见代码)。
       2)前后指针法:通过两个指针走的快慢不同将单趟集合元素分出大于key和小于key两个区间,具体实现见代码。

快排在时间复杂度上的优化:
       1)三数取中法:对key值的选取一般为最右端的值,但最右端得值若是最大或最小值,则此次单趟排序没意义,故取最左端、中间、最右端三个值当中的中间大小的值与最右端值进行交换,可以保证每趟排序不会出现没有元素交换的现象。
       2)小区间优化法:当快排递归到比较小的区间进行排序时,可选用插入排序等排序方式代替快排,减小空间和调用函数栈帧时间上的开销。

非递归实现快排:
       用栈结构模拟递归的调用过程实现,每次单趟排序从栈里取需要排序的区间,并将单趟排序得到的两个小区间分别入栈,等待下次出栈排序,直至栈为空排序结束
代码实现:

//快速排序
//[left,right]闭区间
int PartSort(int *a, int left, int right)  //单趟排序
{
	//int key = a[right];   //挖坑法
	int key = right;        //左右指针法
	while (left < right)
	{
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		//a[right] = a[left];
		while (left < right && a[right] >= a[key])
		{
			right--;
		}
		//a[left] = a[right];
		swap(a[left],a[right]);
	}
	swap(a[left], a[key]);
	//a[left] = key;
	return left;
}
int PartSort2(int *a, int begin, int end)    //前后指针法
{
	int cur = begin;
	int prev = cur - 1;
	while (cur < end)
	{
		if (a[cur] < a[end] && ++prev != cur)
		{
			swap(a[cur], a[prev]);
		}
		cur++;
	}
	swap(a[++prev], a[end]);
	return prev;
}

void QuickSort(int *a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if (end - begin < 5)   //优化---对深层次递归优化为其他排序方式(减少空间开销)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int mid = PartSort2(a, begin, end);
		QuickSort(a, begin, mid - 1);
		QuickSort(a, mid + 1, end);
	}
}
//非递归快排
void QuickSortNonR(int *a, int begin, int end)
{
	stack<int> s;
	s.push(end);
	s.push(begin);

	while (!s.empty())
	{
		int left = s.top();
		s.pop();
		int right = s.top();
		s.pop();

		int mid = PartSort(a, left, right);
		if (left < mid - 1)    //当区间只有一个元素进行排序时不用入栈
		{
			s.push(mid - 1);
			s.push(left);
		}
		if (mid + 1 < right)
		{
			s.push(right);
			s.push(mid + 1);
		}
	}
}

7、归并排序
      将两段有序区间归并为一段有序的区间,但归并排序在空间上会有消耗(开辟同等大小的缓冲区),更适合外排序,
      时间复杂度:O(N*lgN)
      空间复杂度:O(N)
代码实现:

//归并排序
void _Merge(int *a, int *tmp, int begin, int mid_left, int mid_right, int end)
{
	int tmp_begin = begin;
	int left_begin = begin;
	int left_end = mid_left;
	int right_begin = mid_right;
	int right_end = end;
	if (left_begin >= right_end)
		return;
	while (left_begin <= left_end && right_begin <= right_end)
	{
		if (a[left_begin] > a[right_begin])
		{
			tmp[tmp_begin++] = a[right_begin++];
		}
		else
		{
			tmp[tmp_begin++] = a[left_begin++];
		}
	}
	if (left_begin > left_end)
	{
		memcpy(tmp + tmp_begin, a + right_begin, sizeof(a[0]) * (right_end - right_begin + 1));
	}
	else
	{
		memcpy(tmp + tmp_begin, a + left_begin, sizeof(a[0]) * (left_end - left_begin + 1));
	}
	memcpy(a + begin, tmp + begin, sizeof(a[0]) * (end - begin + 1));
}
void MergeSort(int *a, int *tmp, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int mid = begin + ((end - begin) >> 1);
	MergeSort(a, tmp, begin, mid);
	MergeSort(a, tmp, mid + 1, end);
	_Merge(a, tmp, begin, mid, mid + 1, end);
}

对各种排序的比较:
     时间复杂度都为N^2的三个排序里,效率高低的相对排序(最好情况)
    插入排序  >  冒泡排序 > 选择排序

稳定的排序(三种):
    插入排序、冒泡排序、归并排序


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值