排序算法

交换排序:

1.冒泡排序

基本思想:每次比较相邻两个数,如果他们的顺序错误就把他们调换回来。
核心代码:

for(int i=1;i<=n-1;i++)//n个数排序,进行n-1趟比较
{
	for(int j=1;j<=n-i;j++)//从第一个数开始,比较n-i趟,每一趟结束后确定最后一个值为最小值,所以第i趟时比较n-i趟
	{
		if(a[j]<a[j+1])//从大到小排列
		{
			t=a[j];
			a[j]=a[j+1];
			a[j+1]=t;
		}
	}
}	

2.快速排序

假设现在对“6 1 2 7 9 3 4 5 10 8”这10 个数进行排序。
以第一个数6 作为基准数。接下来,需要将这个序列中所有比基准数大的数放在6 的右边,比基准数小的数放在6 的左边,类似下面这种排列。

3 1 2 5 4 6 9 7 10 8

方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6 的数,再从左往右找一个大于6 的数,然后交换它们。这里可以用两个变量i 和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i 指向序列的最左边(即i=1),指向数字6。让哨兵j 指向序列的最右边(即j=10),指向数字8。
首先哨兵j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j 先出动,这一点非常重要因为如果选取最左边的数arr[left]作为基准数,那么先从右边开始可保证i,j在相遇时,相遇数是小于基准数的,交换之后temp所在位置的左边都小于temp。但先从左边开始,相遇数是大于基准数的,无法满足temp左边的数都小于它。所以进行扫描,要从基准数的对面开始。哨兵j 一步一步地向左挪动(即j–),直到找到一个小于6 的数停下来。接下来哨兵i 再一步一步向右挪动(即i++),直到找到一个大于6的数停下来。最后哨兵j 停在了数字5 面前,哨兵i 停在了数字7 面前。
现在交换哨兵i 和哨兵j 所指向的元素的值。交换之后的序列如下。

6 1 2 5 9 3 4 7 10 8

到此,第一次交换结束。接下来哨兵j 继续向左挪动(再次友情提醒,每次必须是哨兵j 先出发)。他发现了4(比基准数6 要小,满足要求)之后停了下来。哨兵i 也继续向右挪动,他发现了9(比基准数6 要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。

6 1 2 5 4 3 9 7 10 8

第二次交换结束,“探测”继续。哨兵j 继续向左挪动,他发现了3(比基准数6 要小,满足要求)之后又停了下来。哨兵i 继续向右移动,糟啦!此时哨兵i 和哨兵j 相遇了,哨兵i 和哨兵j 都走到3 面前。说明此时“探测”结束。我们将基准数6 和3 进行交换。交换之后的序列如下

3 1 2 5 4 6 9 7 10 8

到此第一轮“探测”真正结束。此时以基准数6 为分界点,6 左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j 的使命就是要找小于基准数的数,而哨兵i 的使命就是要找大于基准数的数,直到i 和j 碰头为止。
OK,解释完毕。现在基准数6 已经归位,它正好处在序列的第6 位。此时我们已经将原来的序列,以6 为分界点拆分成了两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列,因为6 左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6 左边和右边的序列即可。
快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。

#include <stdio.h>
int a[101],n;//定义全局变量,这两个变量需要在子函数中使用
void quicksort(int left,int right)
 {
  int i,j,t,temp;
  if(left>right)
   return;
  temp=a[left]; //temp中存的就是基准数
  i=left;
  j=right;
  while(i!=j)
  {
  //顺序很重要,要先从右往左找
   while(a[j]>=temp && i<j)
    j--;
  //再从左往右找
   while(a[i]<=temp && i<j)
    i++;
   //交换两个数在数组中的位置
   if(i<j)//当哨兵i和哨兵j没有相遇时
   {
    t=a[i];
    a[i]=a[j];
    a[j]=t;
   }
  }
//最终将基准数归位
 a[left]=a[i];
 a[i]=temp;
quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程
quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程
}
int main()
{
 int i,j,t;
 //读入数据
 scanf("%d",&n);
 for(i=1;i<=n;i++)
 scanf("%d",&a[i]);
 quicksort(1,n); //快速排序调用
 //输出排序后的结果
 for(i=1;i<=n;i++)
 printf("%d ",a[i]);
 getchar();getchar();
return 0;
}
//可以输入以下数据进行验证。
//10
//6 1 2 7 9 3 4 5 10 8
//运行结果是:
//1 2 3 4 5 6 7 8 9 10

插入排序:

1.直接插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
具体方法如下

以一下数列为例:

3 4 8 2 6 9 7

  1. 从第一个元素开始,该元素可被认为已被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;

在此例中,取出元素2,并对2前元素从后向前扫描

  1. 如果该元素(已排序)大于新元素,将该元素移到下一位置;

3 4 () 8 6 9 7

  1. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

3 () 4 8 6 9 7

() 3 4 8 6 9 7

  1. 将新元素插入到该位置后;

2 3 4 8 6 9 7

  1. 重复步骤2~5。

重复步骤2,取出元素6,对6前元素从后向前扫描

重复步骤3,2 3 4 () 8 9 7

重复步骤4,5 得 2 3 4 6 8 9 7

再次重复,得2 3 4 6 7 8 9

void InsertionSort(int *arr, int size)    
{    
    int i, j, temp;    
    for (i = 1; i < size; i++) {    
        if (arr[i] < arr[i-1]) {    
            temp = arr[i]; //temp为取出的新元素   
            for (j = i - 1; j >= 0 && arr[j] > temp; j--) //已排序的元素大于新元素 
   {  
                arr[j+1] = arr[j];//将已排序的元素移到该元素的下一位置    
            } //此循环结束,找到已排序的元素小于或者等于新元素的位置 
            arr[j+1] = temp;//将新元素插入到该位置后    
        }          
    }    
} 

2.希尔排序

希尔排序可以说是插入排序的一种变种。无论是插入排序还是冒泡排序,如果数组的最大值刚好是在第一位,要将它挪到正确的位置就需要 n - 1 次移动。也就是说,原数组的一个元素如果距离它正确的位置很远的话,则需要与相邻元素交换很多次才能到达正确的位置,这样是相对比较花时间了。
希尔排序就是为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序
希尔排序的思想是采用插入排序的方法,先让数组中任意间隔为 increment 的元素有序,刚开始 increment 的大小可以是 increment = n / 2,接着让 increment = n / 4,让 increment 一直缩小,当 incremen = 1 时,也就是此时数组中任意间隔为1的元素有序,此时的数组就是有序的了。
以以下数组为例:

5 12 35 42 11 2 9 41 26 18 4

初始增量increment=n/2=5,意味着整个数组分为五组,[5, 2, 4][12, 9][35, 41][42, 26][11, 18]
对这五组分别进行直接插入排序,结果可以看到,像2,9,35这些小元素都被调到前面

2 9 35 26 11 4 12 41 42 18 5

缩小增量increment = 5/2 =2,数组被分为两组,[2, 35, 11, 12, 42, 5][9, 26, 4, 41, 18]
对这两组分别进行直接插入排序,结果得

2 4 5 9 11 18 12 26 35 41 42

缩小增量increment = 2/2=1;数组分为一组,[2, 4, 5, 9, 11, 18, 12, 26, 35, 41, 42]进行直接插入排序,结果得

2 4 5 9 11 12 18 26 35 41 42

代码如下

#include<stdio.h>
void ShellSort(int *arr, int size)  
{ 
    int i, j, tmp, increment;  
    for (increment = size/ 2; increment > 0; increment /= 2) {    
        for (i = increment; i < size; i++) {  
            tmp = arr[i];  
            for (j = i - increment; j >= 0 && tmp < arr[j]; j -= increment) {  
                arr[j + increment] = arr[j];  
            }  
            arr[j + increment] = tmp;
        }  
    }  
} 
int main() {
    int a[] = { 5,12,35,42,11,2,9,41,26,18,4 };
    int n = sizeof(a) / sizeof(int);//计算数组a中整数的个数 
    ShellSort(a, n);
    printf("排序好的数组为:");
    for (int j = 0; j < n; j++) {
        printf("%d ", a [j]);
    }
    return 0;
}

需要注意的是,对各个分组进行插入的时候并不是先对一个组排序完了再来对另一个组排序,而是轮流对每个组进行排序。
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序

选择排序:

1.直接选择排序

首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序。
代码如下:

#include <stdio.h>
void Selectsort(int *array, int len)
{
 for(int i = 0; i<len-1; ++i)
 {
  int min = i;
  for(int j = i+1; j<len; ++j)
  {
   if(array[min] > array[j])
   {
    min = j;//记录最小值的位置 
   }
  }
  int temp = array[i];
  array[i] = array[min];
  array[min] = temp; 
 }
}
int main()
{
 int a[] = { 5,12,35,42,11,2,9,41,26,18,4 };
    int n = sizeof(a) / sizeof(int);//计算数组a中整数的个数 
    Selectsort(a, n);
    printf("排序好的数组为:");
    for (int j = 0; j < n; j++) {
        printf("%d ", a [j]);
    }
    return 0;
}

2.堆排序(heep)

堆的特点就是堆顶的元素是一个最值,大顶堆的堆顶是最大值,小顶堆则是最小值。
堆排序就是把堆顶的元素与最后一个元素交换,交换之后破坏了堆的特性,我们再把堆中剩余的元素再次构成一个大顶堆,然后再把堆顶元素与最后第二个元素交换….如此往复下去,等到剩余的元素只有一个的时候,此时的数组就是有序的了。
先将某无序数组调整成二叉堆(大顶堆或小顶堆),再将堆顶元素和堆尾元素交换位置,再调整成二叉堆。

#include <stdio.h>  
/* 交换元素 */  
void  swap (int array[], int i, int j)
{ 
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}
void  printAaray(int array[] ,int len){
       for(int i=0; i < len; i++){
         printf("%d  ", array[i]); 
        }
       printf("\n");
}
/* 调整堆 */  
void heap_ajust(int arr[], int start, int end) { 
    //建立父节点下标和子节点下标  
    int dad = start;  
    int son = dad * 2 + 1;  
    while (son <= end) { //   
        if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两個子节点大小,选择最大的  
            son++;  
        if (arr[dad] > arr[son]) //如果父节点大于子节点代表调整完毕,直接跳出函数  
            return;  
        else { //否则交换父子內容再继续子节点和孙节点比较  
            swap(arr,dad,son);  
            dad = son;  
            son = dad * 2 + 1;  
        } 
    }  
}  
/* 堆排序 */  
void heap_sort(int arr[], int len) { 
    int i;  
    //初始化堆,i从最后一個父节点开始调整  
     // 建立最大堆 
    for (i = len / 2 - 1; i >= 0; i--) {
        heap_ajust(arr, i, len - 1);  
    }       
    //先将第一個元素和已排好元素前一位做交换,再从新调整,直到排序完毕  
    for (i = len - 1; i > 0; --i) {  
        swap(arr,0,i);
        heap_ajust(arr, 0, i-1);  
    }  
}  
int main(int argc, char const *argv[]) {  
   int arr[]={1,34,6,21,98,31,7,4,36,47,39,45,5,2};
    int length = sizeof(arr) / sizeof(int);  
    /* sort */  
    heap_sort(arr, length);  
    /* print Array */   
    printAaray(arr,length);
    return 0;  
}  

性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
代码来源

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值