常见的排序算法

常见的排序算法包括:直接插入排序  、希尔排序、  选择排序    、堆排 、快排 、归并排序;

本篇文章将围绕着各种算法的思想、实现以及时间复杂度进行说明。(均已升序为例)

一、直接插入排序

 思想:

 就是将一个数,往一段有序区间内插入,通过调整使得插入之后的区间有序;


代码实现
//直接插入排序 时间复杂度 O(N^2)  最好 O(N) 本身已经有序 ;最坏O(N^2) 逆序 
void InsertSort(int* a,size_t  n)
{
	for(int i = 0 ;i < n - 1; i++)
	{
	  int end = i ;
	  int tmp = a[end+1];
	  while(end >= 0 && tmp < a[end])  // 注意end <= 0 ;和第一个数也要进相比较
	  {
		a[end + 1] = a[end];
		--end;
	  }
	  a[end+1] = tmp; //end < 0 或者本身有序
	}
}
时间复杂度: O(N^2)

最好的情况下(顺序)时间复杂度为O(N)

最坏的情况下(逆序)时间复杂度为O(N^2)

二、希尔排序

思想:

希尔排序是对直接插入排序的改进,

希尔排序分为两部分:1、预排序       2、直接插入排序;

希尔排序引进了一个参数:gap 表示间距;即按照gap将所需排序的数,分组,然后在组中进行排序(预排序);

注意 :

 gap 越大时,后面的数越容易到达前面,预排序效果不明显;

 gap越小时,预排序效果明显,但后面的数不易到前面;

因此 gap的选择很重要,一般 gap = n / 3   + 1; (加一保证了最后一次是直接插入排序)


//希尔排序 对直接插入排序的优化
//1.预排序
//2.直接插入排序
void ShellSort(int* a,int n)   //时间复杂度 平均O(N^1.3); 最坏情况(已排序O(N^2))
{
	int gap = n ;
	while(gap > 1)
	{
        gap = gap / 3 + 1; //分组的间距 + 1 确保了最后一次一定是直接插入排序
	    for(int i = 0 ;i < n - gap ;i++)
	    {
		  int end = i;
	      int tmp = a[end + gap];
	      while(end >= 0 && a[end] > tmp)
	      {
		     a[end + gap] = a[end];
		     end -= gap;
	      }
	     a[end + gap] = tmp;
	    }
	}
}
时间复杂度 :平均O(N^1.3)

三、选择排序

思想:

1、在一段区间内将找出最小的数,将其放在开始,找出最大的数,将其放在最后;

 2、然后缩小区间(起始位置为刚才区间的后一个,终止为刚才区间末尾的前一个),执行 第1步;

重复以上操作直到区间长度为1,则已排好序;

代码:
//选择排序
//时间复杂度 O(N^2)
void SelectSort(int* a,size_t n)
{
	int begin = 0;
	int end = n - 1 ;
	while(begin < end)//多个区间
	{
		int min = begin;
		int max = end;
	    for(int i = begin ; i <= end ;i++)   //一个期间寻找最大值和最小值
	    {
		   if(a[i] < a[min])
			 min = i;
		   if(a[i] > a[max])
			 max = i;
	    }
	   swap(a[begin],a[min]);
	   if(begin == max)
	   {
		  max = min;   // a[max]已经被换掉了
	   }
	   swap(a[end],a[max]);
       ++begin;
	   --end;
	}
}
时间复杂度: O(N^2)

四、堆排序

思想:

首先创建一个大堆

第一步:将堆顶的数(最大值)与最后一个数进行交换,这样便将最大的数放到了最后;

第二步:将最后一个数之前的数看成一个新的堆,然后调整,使其再一次成为大堆;

第三步:重复第一步;直到只剩下一个数;

代码:

void AdjustDown(int* a,int root,size_t n) //向上调整算法
{
	int parent = root;
	int child = parent * 2 + 1;
	while(child < n)
	{
		if(child + 1 < n && a[child] < a[child+1])
			++child;  //注意
		if(a[parent] < a[child])
		{
			swap(a[parent],a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
//堆排的时间复杂度:NlogN
void HeapSort(int* a,size_t n)
{
	for(int i = (n - 2)/2 ;i >= 0;i--)
	{
		AdjustDown(a,i,n); //建堆 logN
	}
	int end = n - 1;
	while(end > 0)
	{
		swap(a[0],a[end]);  //将最大的数放在最后
		AdjustDown(a,0,end);
		--end;
	}
}

时间复杂度: O(N logN )    logN:表示log以二为底N的对数
五、快速排序

思想:

在一段区间中选择一个数为key,使得比它小的数都在它的左边,比它大的都在它的右边;将它的左边看成一个区间,将它的右边看成一个区间,即子问题;直到区间长度为一

代码:
void QuickSort(int* a,int left ,int right)
{
	if( left >= right)
	  return;
	int div = part3(a,left,right);
	QuickSort(a,left,div-1);
	QuickSort(a,div+1,right);
	
}

在一段区间中选择一个数为key,使得比它小的数都在它的左边,比它大的都在它的右边;可以利用三种方法来实现;

1、左右指针法

思想:选择区间的最后一个元素作为key,区间的开始为begin,结束为end;begin寻找比key大的值,end找比key小的值,将begin和end对应的值进行交换;

int part1(int* a,int begin ,int end)  
{
   int& key = a[end];
   while(begin < end)
   {
	   while(begin < end && a[begin] <= key)
		   ++begin;
	   while(begin < end && a[end] >= key)
		   --end;
	   swap(a[begin],a[end]);
   }
   swap(a[begin],key);
   return begin;
}

2、挖坑法

int part2(int* a,int begin ,int end) //挖坑法 
{
	int key = a[end];
	int tmp = end;
	while(begin < end)
	{
		while(begin < end && a[begin] <= key)
			++begin;
		a[tmp] = a[begin];
		tmp = begin;
		while(begin < end && a[end] >= key)
		   --end;
		a[tmp] = a[end];
		tmp = end;
	}
	a[tmp] = key;
	return begin;
}

3、前后指针法

思想:cur找比key小的值,找到之后使prev++;cur与prev不同的时候交换其各自所对应的值(因为cur总在prev之前,当cur与prev不同时,prev所对应的的值总是比key大的值,而cur对应的值总是比key小的值,这样一交换之后便将比key大的值放到了key之后)

int part3(int* a ,int begin,int end)
{
	int& key = a[end];
	int cur = begin;
	int prev = begin - 1;
	while(cur < end)
	{
		if(a[cur] < key && ++prev != cur)
		{
			swap(a[prev],a[cur]);
		}
		++cur;
	}
    swap(a[++prev],key);
	return prev;
}
空间复杂度 : O(logN)
时间复杂度: O(N logN )    logN:表示log以二为底N的对数

从上图可以看出快排的时间复杂度与key的选择有很大的关系,

快排的优化:

1、三数取中法:使得key的选择更加合理

//key的选择 优化:三数取中法
int GetMid(int* a,int begin,int end)
{
	int mid = begin + ((begin - end) << 1 );
	if(a[begin] > a[mid])     //  begin mid
	{
		if(a[end] > a[begin])    //end  begin  mid
			mid = begin;
		else if(a[end] > a[mid])  //  begin  end  mid
			mid = end;
	}
	else       // mid begin 
	{
	   if(a[begin] > a[end])  // mid  begin end
		  mid = begin;
	   else if(a[end] < a[mid]) // mid end begin
		   mid = end ;
	}
	return mid;
}

2、小区间优化 : 当区间很小时,可以使用直接插入排序

以上的快排都是使用递归方法来实现的,递归对栈的开销太大,

下面使用循环来实现快排:
思想:每次将所需要的区间起始,结束位置压入自己建的栈中,利用循环来实现快排
代码:
//快排的非递归: 递归会消耗堆栈
void QuickSortR(int* a,int left,int right)
{
	stack<int> s;
	if( left < right)
	{
	   s.push(right);
	   s.push(left);
	}
	while(!s.empty())
	{
		int begin = s.top();
		s.pop();
		int end = s.top();
		s.pop();
		int index = part1(a,begin,end);
		if(begin < index - 1)
		{
		  s.push(index - 1);
		  s.push(begin);
		}
		if(index + 1 < end)
		{
			s.push(end);
			s.push(index + 1);
		}
	}
}

六、归并排序

思想:

若两个区间都是有序区间时,将这两个区间进行合并,使其成为有序区间

 代码:
//归并排序
void _Merger(int* a,int* tmp,int begin1,int end1,int begin2,int end2)
{
	int index = begin1;
	int start = begin1;
	int finish = end2;
	//合并两个有序区间
	while(begin1 <= end1 && begin2 <= end2)  //[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++];
	//void *memcpy( void* dest, const void* src, size_t count );

	while(start <= finish)
	{
		a[start] = tmp[start];
		++start;
	}
	//memcpy(a + start , tmp + start, finish - start + 1 );
	
}
void _MergerSort(int* a,int* tmp,int begin,int end)
{
	if(begin < end)
	{
		int mid = begin + ((end - begin) >> 1);
		_MergerSort(a,tmp,begin,mid);
		_MergerSort(a,tmp,mid + 1,end);
		// [begin,mid] ,[mid+1,end] 已经有序
		_Merger(a,tmp,begin,mid,mid+1,end);
	}
}
void MergerSort(int* a,size_t n)
{
	assert(a);
	int* tmp = new int[n];
	_MergerSort(a,tmp,0,n-1);
	delete[] tmp;
}
时间复杂度: O(N logN )    logN:表示log以二为底N的对数
空间复杂度 : O(N)



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值