排序算法

一、插入排序

1.直接插入排序

         最开始选取一个有序区间,例如红色部分,这里认为第一个数就是有序区间,然后让第二个数往有序区间里插入,按升序排列,5比2大,放在其后,重新构成一个有序区间,依次再把后面的4拿出来,可以先放在定义一个tmp里,再与前面的有序区间比较,这里定义一个end,4比5小,5就放在end+1处,end-- 到2处,4再与之比较,大于放在其后,以此类推,直至排好。end--的结束条件

 

void InsertSort(int* a, size_t n)
{
	assert(a);
	for (size_t i = 0; i < n-1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		for (; end >= 0;end--)
		{
			if (a[end] > tmp)
				a[end + 1] = a[end];
			else
				break;
		}
		a[end + 1] = tmp;
	}
}

 

时间复杂度:O(N^2)

  直接插入排序的劣势:如果数组是近似逆序的数组它的效率就比较低了。

   对于接近有序的就比较好,挪动数据挪的少,效率就高了。

 

2.希尔排序(是 直接插入排序 的优化)

  (1)预排序  先定义一个 gap,比如 gap = 3,gap是几就会被先分成几组,每组的数据 n/gap

 

       先排红线所指的数据, 6 拿出来给 tmp ,同 end=9 作比较,6 小于 9 ,9 往后挪,不是挪一个位置而是 gap 个位置,然后 end减 gap,即此时end在数组的前面-3处;end又放在 9 现在的位置,就是图中 end’的位置;然后 3再跟 9 、6 比较,1 再和 9、6、3比较;同样的,5 和 8 比较,交换后,2 和8、5 比较交换;最后换绿线排序。为了接近有序,小的数基本都在前面了。

   (2)插入排序

结合起来的代码是

 

void ShellSort(int* a, size_t n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (size_t i = 0; i < n - gap; i++)  //代码中走的流程是 9 和 5 比较交换,接下来是 8 和 4
		{                                    //比较交换,依次进行,所以循环 n-gap 次,
			int end = i;
			int tmp = a[end + gap];
			for (; end >= 0; end -= gap)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}                               
	}	                             
} 代码中走的流程是 9 和 5 比较交换,接下来是 8 和 4
		{                                    //比较交换,依次进行,所以循环 n-gap 次,
			int end = i;
			int tmp = a[end + gap];
			for (; end >= 0; end -= gap)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}                               
	}	                             
} 

 

 

      时间复杂度:O(N)<希尔<O(N^2)  具体在N^1.25--1.6N^1.25之间

      劣势:数据已经有序或接近有序、逆序,希尔排序会起到反作用;如果不是接近有序且数据量比较大,它就会比插入排序更优一些。

二、选择排序

 (1)选择排序

        遍历一遍,找出最小的数的下标,把它与第一个数交换,然后除过第一个数,再遍历一遍,找出最小的数,与本次遍历的第一个数交换,依次这样。

选择排序的优化思想:

 

     第一次选出两个数放在两边,然后缩小区间,再选出两个数放两边,再缩小区间,再选,直到两个下标相遇或者错过的时候停止。

void SelectSort(int* a, size_t n)
{
	assert(a);
	size_t begin = 0;
	size_t end = n - 1;
	while (begin < end)
	{
		size_t max = begin, min = begin;
		for (size_t i = begin; i <= end; i++)

		{
			if (a[i] > a[max])
			{
				max = i;
			}
			if (a[i] < a[min])
			{
				min = i;
			}
		}
		swap(a[begin], a[min]);
		//如果max占据了begin位置,则更正max的位置
		if (begin == max) //针对特殊情况,比如 9、5、4、2、3、6、8、7、1、0最大和最小的分别占据两边,那么
		{                  //执行这两个交换语句 之后,两个数据又被换回了原来的位置,所以需要中间的if语句来
			max = min; //更正。
		}
		swap(a[end], a[max]);
		++begin;
		--end;
	}
}
针对特殊情况,比如 9、5、4、2、3、6、8、7、1、0最大和最小的分别占据两边,那么
		{                  //执行这两个交换语句 之后,两个数据又被换回了原来的位置,所以需要中间的if语句来
			max = min; //更正。
		}
		swap(a[end], a[max]);
		++begin;
		--end;
	}
}

时间复杂度:O(N^2)

      选择排序是最坏的排序算法,因为就算是有序,也是N^2的效率,但是插入排序对于接近有序的数据可以不用交换。

      直接选择排序是最直观的

 

(2) 堆排序

         静态二叉树的思想,最适合完全二叉树。排一个升序,建大堆,写一个向下调整算法

       不要把 搜索树 和 大、小堆搞混了:搜索树是左边小,右边大;大堆是根节点大于孩子,左右孩子的大小无所谓,小堆相反。

void AjustDowd(int* a, size_t n, int root)
{
	assert(a);
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			++child;
		}
		if (a[child] > a[parent])
		{
			swap(a[child], a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}


void HeapSort(int* a, size_t n)
{
	assert(a);
	//建堆
	for (int i = (n-2)/2; i >= 0; --i)
	{
		AjustDowd(a, n, i);//向下调算法
	}
        //排序
        int end = n - 1;
	while (end > 0)
	{
		swap(a[0], a[end]);
		AjustDowd(a, end, 0);
		--end;
	}
}

时间复杂度:O(N*logN)

  向下调整算法是 logN,在循环里是 N*logN,有两个则是 2N*logN,简化到O(N*logN)

三、交换排序

   (1)冒泡排序

 

void BubbleSort(int* a, size_t n)
{
	assert(a);
	for (size_t end = n; end > 0; --end)
	//for (size_t end = 0; end < n; ++end)  //一开始的错误写法
	{
		int exchange = 0;
		int first = 0, second = 1;
		while (second < end)
		{
			if (a[first] > a[second])
			{
				swap(a[first], a[second]);
				exchange = 1;
			}
			++first;
			++second;
		}
		if (exchange = 0)
			break;
	}	
}

 

时间复杂度:O(N)-O(N^2)

(2) 快速排序

      先选出一个 Key 然后放到它对的位置,还有两个指针,一个在头begin一个尾end(左右指针法),让begin先走找大的值,找到大的与end的交换,然后再重复找,同时end也在找比Key小的值,找到后begin、end再交换,再走,到他们相遇就停止,而且停止的位置一定是比Key大的位置,最后将相遇的位置与Key一交换,此时就是左边都是比Key小的,右边都是比它大的,即Key就放在了正确的位置上

划分到足够小,直到有序,其实快排是一个递归

I.左右指针法

 

int PartSort(int* a, int begin, int end)
{
	int key = end;
	while (begin < end)
	{
		while (begin < end && a[begin] <= a[key])//begin找到比key大的后停止,执行下面的while循环,等到end
			++begin;        
		while (begin < end && a[end] >= a[key])//找到小的后,交换;注意要用 >= 和<= 否则数组遇到相同的数据
			--end;                         //会造成死循环
		if (begin < end)
			swap(a[begin], a[end]);
	}
	swap(a[begin], a[key]);
	return begin;
}
//闭区间[left,right]
void QuickSort(int* a, int left, int right)
{
	assert(a);
	if (left < right)//保证这个区间至少有两个及以上的数
	{
		int div = PartSort(a,left,right);
		QuickSort(a, left, div - 1);
		QuickSort(a, div+1, right);
	}
}

begin找到比key大的后停止,执行下面的while循环,等到end
			++begin;        
		while (begin < end && a[end] >= a[key])//找到小的后,交换;注意要用 >= 和<= 否则数组遇到相同的数据
			--end;                         //会造成死循环
		if (begin < end)
			swap(a[begin], a[end]);
	}
	swap(a[begin], a[key]);
	return begin;
}
//闭区间[left,right]
void QuickSort(int* a, int left, int right)
{
	assert(a);
	if (left < right)//保证这个区间至少有两个及以上的数
	{
		int div = PartSort(a,left,right);
		QuickSort(a, left, div - 1);
		QuickSort(a, div+1, right);
	}
}

 

II.挖坑法

       begin ,end 同样指向头和尾,选了end位置作了 key,先把key保存到另一个变量里,如上图,tmp=5;所以5这个位置就空了,当begin 找到大 的时候,就放在空的这个位置,end再找小,放在刚大 的那个空的位置上,然后begin找,end找,不断循环,begin,end相遇就停下来,把key放入这个坑。

int PartSort2(int* a, int begin, int end)
{
	int tmp = a[end];
	while (begin < end)
	{
		while (begin < end && a[begin] <= tmp)
			++begin;
		if (begin < end)
			a[end] = a[begin];

		while (begin < end && a[end] >= tmp)
			--end;
		if (begin < end)
			a[begin] = a[end];
	}
	a[begin] = tmp;
	return begin;
}

III.前后指针法

 cur不断往后走,遇到比key小的 ++,同时prev也 ++;当cur遇到比key大的,prev 不++,cur继续往后走,当cur又遇到比key小的,prev++,然后cur与prev的值交换,cur继续往后走直到走到 key处停止,cur等于key,prev也++,最后该交换交换,key也就放到了正确的位置。

 

int PartSort3(int* a, int begin, int end)
{
	int prev = begin - 1;
	while (begin < end)
	{
		while (a[begin] < a[end] && ++prev != begin)
		{
			swap(a[prev], a[begin]);
		}
		++begin;
	}
	swap(a[++prev], a[end]);
	return prev;
}

    对于这三种方法,效率其实差不多,前两种都是大的往后翻,小的往前翻,翻来翻去的,而第三种方法就比较新颖,一前一后走。如果对单链表进行排序,左右指针就不可以用了,而前后指针就可以,对链表排序用冒泡比较好,但是现在可以用快排的第三种方法了。

 

再优化:

I.三数取中法

 

int GetMidIndex(int* a, int left, int right)
{
	int mid = left + ((right - left) >> 1);//取中,不要用两数相加除以二
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])//a[left]>a[mid] a[mid]<a[right]
		{
			return right;
		}
		else
			return left;
	}
	else//a[left]<a[mid]
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[mid] > a[right])//a[left]<a[right]
		{
			return right;
		}
		else
			return mid;
	}
}

 

求得的中点,随便带到前面的哪个方法中都可以优化

II.小区间优化法

 

void QuickSort2(int* a, int left, int right)
{
	assert(a);
	if (left >= right)
		return;
	else if (right - left < 5)
	{
		InsertSort(a + left, right - left + 1);
	}
	else//保证这个区间至少有两个及以上的数
	{
		int div = PartSort1(a, left, right);
		QuickSort2(a, left, div - 1);
		QuickSort2(a, div + 1, right);
	}
}

非递归的写法

 

 

void QuickSortNonR(int* a, int begin, int end)
{
	stack<int> s;
	if (begin < end)
	{
		s.push(end);
		s.push(begin);
	}
	while (!s.empty())
	{
		int left = s.top();
		s.pop();
		int right = s.top();
		s.pop();

		int div = PartSort1(a, left, right);
		if (left < div - 1)
		{
			s.push(div - 1);
			s.push(left);
		}
		if (div + 1 < right)
		{
			s.push(right);
			s.push(div + 1);
		}
	}
}


四、归并排序

 

 

红色部分是拷贝一个同样大小的空间,将归并排好序的分区放入此空间

 

void _Merge(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
	int index = begin1;
	int start = begin1, finish = end2;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
			tmp[index++] = a[begin1++];
		else
			tmp[index++] = a[begin2++];
	}
	while (begin1 <= end1)
		tmp[index++] = a[begin1++];
	while (begin2 <= end2)
		tmp[index++] = a[begin2++];

	memcpy(a + start, tmp + start, (finish - start + 1)*sizeof(int));
}


void _MergeSort(int* a, int* tmp, int left, int right)
{
	if (left >= right)
		return;

	int mid = left + ((right - left) >> 1);

	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid + 1, right);

	_Merge(a, tmp, left, mid, mid + 1, right);
}


void MergeSort(int* a, int n)
{
	assert(a);
	int* tmp = new int[n];

	_MergeSort(a, tmp, 0, n-1);

	delete[] tmp;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值