C语言-----八大排序

八大排序

一、冒泡排序:

<1>时间复杂度:O(n^2);
(1)最差情况:O(n^2) 无序
(2)最好情况:O(n) 有序
<2>空间复杂度:O(1);
<3>稳定性:稳定排序。 例如:5、5、3 第一趟之后为5、3、5 第一个5仍然在第二个5后面
<4>基本思想:将相邻的两个数比大小,将小的调到前面。
例如:有一行数分别是26、10,83,56,28,66,7
//方框数为比较的次数
(1)第一躺排序:
在这里插入图片描述
此时已将此行数中最大的那个数字放在末尾。
(2)第二躺排序:
在这里插入图片描述
此时将倒数第二大的数字放在后面。
(3)第三趟排序:
在这里插入图片描述
此时将倒数第三大的数字放在后面。
以此类推,可以得到最终结果为:7、10、26、28、56、66、83。
备注:n个数必要比较n - 1趟,第一躺比较n-1次,第 j 趟需要比较n - j 次。
<5>代码实现:
1.无序情况:

void Bubble_Sort(int *arr,int len)              //其中len是数组的长度
{
     for(int i = 0;i < len - 1;i++)                 //i表示趟数
     {
         for(int j = 0;j < len-i-1;j++)            //j表示次数
         {
              if(arr[j] > arr[j+1])
              {
                    int temp;                     //交换
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
              }
         }
     }
}

2.有序情况:

void Butter_Sort2(int *arr,int len)
{
	 bool swap = false;                       //设置一个标志位
     for(int i = 0;i < len - 1;i++)          //i表示趟数
     { 
         for(int j = 0;j < len-i-1;j++)             //j表示次数
         {
              if(arr[j] > arr[j+1])
              {
                    int temp;                        //交换
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
					swap = true;                //标志位置为true
              }
         }
		 if(!swap)             //当一次躺比较没有一次进入第一个if语句时,才会
		 {                         //进入到这个if语句,说明此时序列已经是有序数列
			 return;
		 }
     }
}

二、 选择排序

<1>时间复杂度:O(n^2);
<2>空间复杂度:O(1);
<3>稳定性:不稳定排序。 例如:5、5、3 在第一次排序后变为 3、5、5,第一个5的位置已经发生改变
<4>基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
例如:88、5、15、56、32、18、69
备注:其中每条彩色线条为两数之间的比较,其条数为比较次数.图示为比较结果,过程较为简单,请读者自行分析.
(1)第一躺比较:
在这里插入图片描述
此时最小数5已经放在最开始的位置。
(2)第二趟比较:
在这里插入图片描述
此时将第二小的数字放在当前序列的开头
(3)第三趟排序:
在这里插入图片描述
此时将第三小的数字放在当前序列的开头
以此类推,可得到最终结果为:5、15、18、32、56、69、88
代码实现:

void  Selset_Sort(int *arr,int len)
{
     for(int i = 0;i < len;i++ )
     {
         for(int j = i+1;j < len;j++)
         {
           if(arr[i] > arr[j])
            {
              int temp;
              temp = arr[i];
              arr[i] = arr[j];
              arr[j] = temp;
            }
         }
     }
}

三.直接插入排序

<1>时间复杂度:O(n^2)
(1)最差情况:O(n^2) 无序
(2)最好情况:O(n) 有序 越有序越快
<2>空间复杂度:O(1)
<3>稳定性:稳定排序
<4>基本思想:如同打扑克牌一样,起初在摸牌的时候,第一张牌对于自身而言是有序的,假如摸到4,当第二次摸到6时,会将6放在4的后面,第三次摸到3时,会将3放在4的前面,这个过程就是直接插入排序。那么对于6和3而言,如何知道是大还是小呢?过程很简单,每摸到一张牌就和前面已有序数列作比较比较,然后交换顺序即可。
例如:8、45、18、33、15、99、3
具体实现步骤如下:设置一个临时变量temp,从二个数开始,相当于摸第二张牌,然后从一直牌开始排序。
备注:1.其中i表示当前所摸的牌,j表示已有序数列的最后一张牌,从后往前比较。
2.其中蓝色空格为temp的值,不代表方框中的值,彩色线条为两数之间的比较.
(1)第一趟排序:
在这里插入图片描述
(2)第二趟排序:
在这里插入图片描述以此类推…
(3)第六趟排序:
在这里插入图片描述在这里插入图片描述
此时待排序列已全部有序。
拓展:n个数排序需要n - 1躺。
代码实现:

void  Insert_sort(int *arr,int len)
{
   for(int i = 1;i < len;i++)
   {
      int temp = arr[i];
      int j = 0; 
      for(j = i-1;j >=0;j--)
      {
         if(arr[j] > temp)
         {
            arr[j+1] = arr[j];
         }
         else
         {
            break;
         }
      }
      arr[j+1] = temp;
   }
}

四、快速排序

<1>时间复杂度:O(nlog2n)
(1)最好情况: O(nlog2n)
(2)最坏情况: O(n^2)              整个序列以及完全有序且为倒序,退化为冒泡排序
<2>空间复杂度:O(log2n)
<3>稳定性:不稳定排序
<4>基本思想:通过一趟排序将要排序的数据分割成独立的两部分,将序列分为两部分的数作为基准,基准左边的数都要比基准右边的数要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
例如:8,15,26,18,7,66,4
分析:
依靠基准将序列分割为两部分,那么基准如何找到呢?
方法有三种:
1.固定位置选取基准法
2.随机选取基准法
3.三分取中法

<5>具体步骤:
一次取基准(par)过程:
在这里插入图片描述
然后对左右两部分子序列继续进行此步骤。(如果子序列为单个数时,说明自身有序,不进行此过程)
代码实现:

int Partion(int *arr,int low,int high)       //一次找基准过程
{
    int temp = arr[low];
    while(low < high)          //当low = high时退出循环,此时的位置为基准位置
    {
       while(low < high && arr[high] > temp)
       {
         high--;                              
       }
       if(low >= high)
       { 
           break;
       }
       else
       {
           arr[low] = arr[high];
       }
       while(low < high && arr[low] < temp)
       {
         low++;
       }
       if(low >= high)
       { 
           break;
       }
       else
       {
           arr[high] = arr[low];
       }
   }
   arr[low] = temp;
   return low;
}

(1)递归实现:

void Quick(int *arr,int start,int end)     
{
    int par = Partion(arr,start,end);      //一次找基准
    if(par > start+1)
    {
        Quick(arr,start,par - 1);
    }
    if(par < end - 1)
    {  
        Quick(arr,par+1,end);
    }
}
void Quick_Sort(int *arr,int  len)      //len为数组的长度
{
    Quick(arr,0,len-1);
}

(2)非递归实现:
进行完一次排序后,将左右子序列的第一个和最后一个元素放在数组中,然后读取元素,重新进行排序。(如果子序列为单个数时,说明自身有序,不进行此过程)
一次入栈过程:
在这里插入图片描述

void Quick_Sort(int *arr,int len)
{
	int tmpSize = (int)log((double)len)/log((double)2);
	int *stack = (int *)malloc(sizeof(int)*tmpSize*2);     //malloc开辟动态空间
	assert(stack != NULL);                //断言
	int top = 0;                         //数组的下标
	int low = 0;
	int high = len - 1;
	int par = Partion(arr,low,high);        //第一次找基准
	if(par > low + 1)
	{
		stack[top++] = low;
		stack[top++] = par - 1;
	}
	if(par < high-1)
	{
		stack[top++] = par + 1;
		stack[top++] = high;
	}
	while(top > 0)   //栈不为空
	{
		high = stack[--top];
		low = stack[--top];
		par = Partion(arr,low,high);
	    if(par > low+1)
	    {
		  stack[top++] = low;
		  stack[top++] = par - 1;
	    }
	    if(par < high-1)
	    {
		  stack[top++] = par + 1;
		  stack[top++] = high;
	    }
	}
	free(stack);      //释放空间
	stack = NULL;
}

上述代码中,取基准的过程,实际上用到的是第一种取基准的方法,下面说明剩下两种方法
(1)随机选取基准法:

void Swap(int *arr,int low,int high)
{
    int temp;
    temp = arr[low];
    arr[low] = arr[high];
    arr[high] = temp;
}
void Rand(int *arr,int low int high)
{
    srand((unsigned int)time(NULL));     //随机种子
    Swap(arr,low,rand() % (high-low)+low);    //取low ~ high之间的任意数字和low发生交换
}

(2)三分取中法

void Mid_of_Three(int *arr,int low,int high)
{
	int mid = (high - low)/2+low ;
	if(arr[mid] > arr[low])             //arr[mid] <= arr[low]
	{
		Swap(arr,mid,low);
	}
	if(arr[low] > arr[high])            //arr[low] <= arr[high]
	{
		Swap(arr,low,high);
	}
	if(arr[mid] > arr[high])            //arr[mid] <= arr[high]
	{
		Swap(arr,mid,high);
	}
}     

关于快速排序还有两种优化方式,下面进行介绍。
一、如果待排序序列,在排序过程当中数据量变很小时,用直接插入排序

void Insert_sort(int *arr,int low,int high)
{
	for(int i = low+1;i <= high;i++)
	{
		int temp = arr[i];
		int j = 0;
		for(j = i-1;j >= low;j--)
		{
			if(arr[j] > temp)
			{
				arr[j+1] = arr[j];
			}
			else
			{
				break;
			}
		}
		arr[j+1] = temp;
	}
}
void Quick(int *arr,int low,int high)
{
	if((high - low)+1 < 100)
	{
		Insert_sort(arr,low,high);
		return;
	}
}
void Quick_Sort(int *arr,int len)
{
	Quick(arr,0,len-1);
}

二、聚集相同元素基准法

void FocusNumPar(int *arr,int low,int par,int high,int *left,int *right)
{
	if(low < high)
	{
		int parLeft = par-1;
		for(int i = par-1;i >= low;i--)
		{
			if(arr[i] == arr[par])
			{
				if(i != parLeft)
				{
					Swap(arr,i,parLeft);
					parLeft--;
				}
				else
				{
					parLeft--;
				}
			}
		}
		*left = parLeft;
		int parRight = par+1;
		for(int i = par+1;i <= high;i++)
		{
			if(arr[i] == arr[par])
			{
				if(i != parRight)
				{
					Swap(arr,i,parRight);
					parRight++;
				}
				else
				{
					parRight++;
				}
			}
		}
		*right = parRight;
	}
}
void Quick(int *arr, int low, int high)
{
    int par = Partion(arr,low,high);
	int left = 0;
	int right = 0;
	FocusNumPar(arr,low,par,high,&left,&right);
	if(left >= low+1)     //说明左边有两个数据以上
	{
		Quick(arr,low,left);
	}
	if(right <= high-1)
	{
		Quick(arr,right,high);
	}
}
void Quick_Sort(int *arr,int len)
{
	Quick(arr,0,len-1);
}

[===>关于快速排序的三种方法及两种优化,读者若未能深入理解,可点击此处(https://blog.csdn.net/insistgogo/article/details/7785038)

五、堆排序

<1>时间复杂度:O(nlog2n)
<2>空间复杂度:O(log2n)
<3>稳定性:不稳定排序
<4>基本思想:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
备注:大根堆是每个结点的值都大于或等于其左右孩子结点的值;小根堆是每个结点的值都小于或等于其左右孩子结点的值。
<5>如图所示:
在这里插入图片描述
而实际上序列的存储形式是一维数组:
如图所示:
在这里插入图片描述
例如:25、15、88、45、8、1、33、21、55、6
<6>具体步骤:
(1)第一次排序:
在这里插入图片描述
(2)第二次排序:
在这里插入图片描述
以此类推,可得最后一次排序情况为:
在这里插dascc述
此时排为大根堆,接下来只需要将根节点和最后一个节点交换值,然后对根节点进行一次排序即可.
如图所示:
在这里插入图片描述
<7>代码实现:

void Adjust (int *arr,int start,int end)              
{
	int temp = arr[start];
	for(int i = 2*start +1;i<= end;i = 2*i+1)          //i是左孩子下标
	{
		//判断是否有右孩子
		if(i < end && arr[i] < arr[i+1])                 //说明有右孩子,左孩子小于右孩子
		{
			i++;
		}
		if(arr[i] > temp)
		{
			arr[start] = arr[i];
			start = i;
		}
		else
		{
			break;
		}
	}
	arr[start] = temp;
}
void Heap_Sort(int *arr,int len)
{
	int i = 0;
	for(i = (len-1-1)/2;i >= 0;i--)
	{
		Adjust(arr,i,len-1);
	}
	//此时已经是大根堆
	for(i = 0;i < len-1;i++)
	{
		int temp = arr[0];
		arr[0] = arr[len-1-i];
		arr[len-1-i] = temp;
		Adjust(arr,0,len-1-i-1);
	}
}

备注:
父====>>子
若父为n,则其右孩子为2n+1;左孩子为2n+2。
子====>>父
若子为n,则其父为(n-1)/2;

六、shell排序<希尔排序>

<1>时间复杂度:O(n^2)
(1)最好:O(n)
(2)最坏:O(n^2)
<2>空间复杂度:O(1)
<3>稳定性:不稳定排序
<4>基本思想:shell排序实际上是一种直接插入排序推广,其先将一组数分成若干组;此处应该注意,分组的方式不能几个几个紧挨着分组,而是采用每次所分组数均为素数且最后一次分组为1的方法,采用分组的好处是,在每次排序完后都是将小的数尽量往前面赶,大的数尽量往后面赶,最后一次排序直接采用直接插入排序。运用到了直接插入排序越有序有快的特性。
例如:12、5、9、34、6、8、33、56、89、0、7、4、22、55、77
具体步骤:
在这里插入图片描述
<5>代码实现:

void Shell(int *arr,int len,int gap)
{
	for(int i = gap;i < len;i++)
	{
		int temp = arr[i];
		int j = 0;
		for(j = i-gap;j >= 0;j-=gap)
		{
		if(arr[j] > temp)
		{
			arr[j+gap] = arr[j];
		}
		else
		{
			break;
		}
		}
		arr[j+gap] = temp;
	}
}     
void Shell_Sort(int *arr,int len)
{
	int drr[] = {5,3,1};     //drr[]为分的组数,这里5,3,1不固定,但必须相互为素数,且最后数为1
	int lend = sizeof(drr)/sizeof(drr[0]);
	for(int i = 0;i < lend;i++)
	{
		Shell(arr,len,drr[i]);
	}
}

七、归并排序

<1>时间复杂度:O(nlog2n)
<2>空间复杂度:O(n)
<3>稳定性:稳定排序
<4>基本思想:先使每个子序列有序,再使子序列段间有序。若将两有个有序表合成一个有序表,成为二路归并。
例如:15、2、35、6、23、11、5
具体步骤:
在这里插入图片描述
备注:其中s1,s2,e1,e2 分别表示两个归并段的首尾位置,brr[]为辅助数组。
<5>代码实现:

void Merge(int *arr,int len,int gap)
{
	int *brr = (int *)malloc(sizeof(int) * len);
	assert(brr != NULL);
	int i = 0;       //brr的下标
	int start1 = 0;
	int end1 = start1+gap-1;
	int start2 = end1+1;
	int end2 = start2+gap-1 < len-1 ? start2+gap-1 : len-1;     //当有两个归并段的时候
	while(start2 < len)
	{
		//当两个归并段还没有比较完的时候
		while(start1 <= end1 && start2<=end2)
		{
			if(arr[start1] <= arr[start2])
			{
				brr[i++] = arr[start1++];
			}
			else
			{
				brr[i++] = arr[start2++];
			}
		}
		while(start1 <= end1)     //如果是第二个归并段比较完了,则将第一个归并段中的数放在brr[]
		{
			brr[i++] = arr[start1++];
		}
		while(start2 <= end2)  //如果是第一个归并段比较完了,则将第二个归并段中的数放在brr[]
		{
			brr[i++] = arr[start2++];
		}
		//找两个新的归并段
		start1 = end2+1;
		end1 = start1+gap-1;
		start2 = end1+1;
		end2 = start2+gap-1 < len-1?start2+gap-1:len-1;
	}
	while(start1 < len)    //如果在最后不存在第二个归并段,则将第一个归并段中的数放在brr[]
	{
		brr[i++] = arr[start1++];
	}
	for(int i = 0;i < len;i++)    //数值拷贝
	{
		arr[i] = brr[i];
	}
}
void MergeSort(int *arr,int len)
{
	for(int i = 1;i < len;i *= 2)
	{
		Merge(arr,len,i);
	}
}

猜你喜欢

转载自blog.csdn.net/FDk_LCL/article/details/84299390