【数据结构】排序算法

排序算法是数据结构的基本算法,也是面试时面试官最喜欢提问的算法。学习排序,不仅要知道它的基本思路,还要了解它的复杂度以及稳定性。同时对于排序代码也要做到信手捏来。否则在面试时会非常尬....

排序分为以下几类:
插入排序,选择排序,堆排序,快速排序,冒泡排序,归并排序。其中插入排序又可分为俩类:直接插入排序和希尔(shell)排序。

1.插入排序
1.1直接插入排序。
   思路:在一个数组中找一个数,拿他依次和前面的值比较,如果比前面的值小,那么把前面这个数往后挪一下,把后面的挪前 来。
  在此中可以找一个以end为右终点的有序区间数组,数组的个数从1开始依次增大到n-2(原数组的大小-1),然后拿end后面的值temp依次跟区间数组中的值进行比较,遇到比temp大的数,就把temp插入到这个数之前...
代码:

void InsertSort(int *a,int n)
{
	assert(a);

	int end = 0;
	int temp = 0;
	int i = 0;
	for(i = 0;i < n-1;++i)
	{
		end = i;
		temp = a[end+1];   //end后面的值赋给temp
		while(end >= 0 && a[end] > temp)
		{
			a[end+1] = a[end];   //后移
			a[end] = temp;   //插入
			end--;
		}
	}
}

从中可以看出,直接插入排序的时间复杂度是O(N*N),空间复杂度O(1)。当数组逆序时,直接插入排序达到最坏情况,这时候有一种新的插入算法叫做shell排序。

shell排序
shell排序是对数组进行预排序,使数组接近于有序。


    从上述代码中可以发现,在最后调用了直接插入排序。有的童鞋可能会说shell排序明明是让算法变得更复杂,因为最后还是得进行一次直插。真的是这样么?NO!稍微回想下你就会记起,我们经常所说的时间复杂度是它最坏
情况时的复杂度,当他最坏情况不再发生时时间复杂度会小于O(N*N),当最好情况时,复杂度是O(N)。有人曾经对shell排序的时间复杂度进行计算,发现它的复杂度是O(N^1.25)~O(1.6N^1.25)。
那么shell排序比直接插入排序好是对的么?不是。shell排序不一定比直接插入排序好,当数组已经有序或者接近有序时直接插入排序更好。shell排序适用于数组反序或接近反序。

选择排序
思路:每次遍历选一个最大(或最小)的数放在数组的右边(或左边),有一种更简洁的方法:每次同时选出最大的和最小的放在它们相应的位置。

void SelectSort(int *a,int n)
{
	assert(a);

	for(int i = 0;i<n/2;i++)
	{
		int min = i;
		int max = n-1-i;

		for(int j = i+1;j<n;j++)
		{
			if(a[j] < a[min])
				min = j;
			if(a[n-j-1] > a[max])
				max = n-j-1;
		}
		if(min != i)  //小的可能被换到后面
		{
			swap(&a[min],&a[i]);
			if(max == i)  //max在下标为0的位置就会被换走,所以需要重新换回来
			{
				max = min;
			}
		}
		if(max != n-i-1)
			swap(&a[max],&a[n-1-i]);
	}
}

  选择排序时间复杂度是O(N*N)。
 
  堆排序  ※
  思路:排升序建大堆。每次把堆向下调整后,都会把最大的数调到堆顶,这时交换堆顶元素和最后的堆底元素,最后去掉堆底元素再进行向下调整,重复上述步骤。
  代码:
 

void AdjustDown(int *a,int size,int root)
{
	assert(a);

	int parent = root;
	int child = parent*2+1;

	while(child < size)
	{
		while(child+1 < size && a[child+1] > a[child])
			child++;

		if(a[parent] < a[child])
		{
			swap(&a[parent],&a[child]);
			parent = child;
			child = parent*2+1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int *a,int n)  //升序建大堆
{
	assert(a);

	int i = 0;
	for(i = (n-2)/2;i >= 0;i--)
	{
		AdjustDown(a,n,i);
	}

	for(i = 0;i < n;i++)
	{
		swap(&a[0],&a[n-1-i]);
		AdjustDown(a,n-i-1,0);
	}
}


  快速排序
  思路:在数组中找一个基准, 使基准左边都比它小,右边都比它大,然后在俩边重复相同的步骤。
  有三种方法:(此先我们先用三数取中法取到最优的中间值)

int GetMidIndex(int *a,int left,int right)  //三数取中法
{
	assert(a);

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

	if(a[left] < a[right])   //left  mid  right ; mid left  right;left right mid
 	{
		if(a[mid] < a[left])
			return left;
		else if(a[right] < a[mid])
			return right;
		else
			return mid;
	}
	else    
	{
		if(a[mid] < a[right])
			return right;
		else if(a[left] < a[mid])
			return left;
		else
			return mid;
	}
}


  1.左右指针法:选右边的值作为中间值key,从左边找个比key大的停下,从右边找个比key小的停下。交换左右俩边的值。这样可以让左边都小右边都大。

//左右指针法
int PartSort1(int *a,int left,int right)
{
	assert(a);

	int mid = GetMidIndex(a,left,right);
	swap(&a[mid],&a[right]);
	int key = a[right];
	int begin = left;
	int end = right;

	while(begin < end)
	{
		if(begin<end && a[begin] <= key)
			begin++;
		if(begin<end && a[end] >= key)  //必须有等于
			end--;
		if(begin < end)
			swap(&a[begin],&a[end]);
	}
	swap(&a[begin],&a[right]);

	return begin;
}


  2.挖坑法:选左右边的为坑,从左边开始找比选定的值大的,然后把这个值放在坑里,这个值原来的位置作为新的坑。然后从右边找比选定的值小的,把这个值放在坑里,一直到左右指针相等,最后把选定的值放在坑里。

//挖坑法
int PartSort2(int *a,int left,int right)
{
    assert(a);
    
	int mid = GetMidIndex(a,left,right);
	swap(&a[mid],&a[right]);
	int key = a[right];
	int ken = right;
	int begin = left;
	int end = right;

	while(begin < end)
	{
		if(begin<end && a[begin]<=key)
		{
			++begin;
		}
		else
		{
			a[ken] = a[begin];
			ken = begin;
		}
		if(begin<end && a[end]>=key)
		{
			--end;
		}
		else
		{
			a[ken] = a[end];
			ken = end;
		}
	}
	a[ken] = key;
	return ken;
}


  3.前后指针法:prev刚开始赋值-1,cur赋值0.cur遇到比选定值大的值就往后走,遇到小的就停下。然后++prev再交换prev和cur的值,使小的往前走,大的往后走。prev始终指向大的值的前一个。

//前后指针法
int PartSort3(int* a,int left,int right)
{
	assert(a);

	int mid = GetMidIndex(a,left,right);
	swap(&a[mid],&a[right]);
	
	if(left < right)
	{
		int key = a[right];

		int cur = left;
		int prev = left-1;
		while(cur < right)
		{
        	if(a[cur] < key)
			{
				++prev;
				if(cur != prev)
				{
					swap(&a[cur],&a[prev]);
				}
			}
			++cur;
		}
		swap(&a[right],&a[++prev]);
		return prev;
	}
	return left;
}

使用最终算法进行递归调用

void QuickSort(int *a,int left,int right) 
{
	assert(a);

	int div = 0;
	if(left >= right)
		return;
	div = PartSort3(a,left,right);
	QuickSort(a,left,div-1);
	QuickSort(a,div+1,right);
}


 
  冒泡排序
  思路:前后俩个数比较,选出最大(或最小)的放在最后。但是有优化,设置一个标志,判断是否已排好序。

void BubbleSort(int *a,int n)
{
	assert(a);

	int i = 0;
	int j = 0;

	for(i = 0;i < n-1;i++)
	{
		int flag = 0;
		for(j = 0;j < n-i-1;j++)
		{
			if(a[j] > a[j+1])
			{
				swap(&a[j],&a[j+1]);
				flag = 1;
			}
		}
		if(flag == 0)
			break;
	}
}


 
  归并排序
  思路:开辟一块空间,把原来的序列每次分一半,直到只有一个值为止。然后一个一个合并。先把排序好的值铐到开辟的空间中,然后在考回原数组,区间每次增加一半,直到增回原来的值。

void _MargeSort(int *a,int left,int right,int* tmp)
{
	assert(a);

	if(left >= right)
		return;

	/*if(right-left < 20)
	{
		QuickSort(a,left,right);
	}*/

	int mid = left + ((right-left)>>1);
	_MargeSort(a,left,mid,tmp);
	_MargeSort(a,mid+1,right,tmp);

	int begin1 = left,end1 = mid;
	int begin2 = mid+1,end2 = right;
	int index = left;  //tmp下标

	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++];
	}

	index = left;
	 while(index <= right)
	 {
		 a[index] = tmp[index];
		 index++;
	 }
}

void MargeSort(int *a,int n)
{
	assert(a);

	int* tmp = (int*)malloc(sizeof(int)*n);
	memset(tmp,0,sizeof(int)*n);

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

	free(tmp);
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值