排序算法 - 选择排序、插入排序、冒泡排序

1、首先我们先看一下选择排序的思想:

每一趟(例如第i趟,i=0,1,2,…n-2)在后面n-i个待排的元素中选出关键字最小的元素,作为有序元素序列的第i个元素。

这段表述乍一听是不容易理解的,我的理解能力比较差,所以学习的时候就直接跳过了。我始终认为对于数据结构来讲,画图永远是最有效、最直观的一种方式,从链表到队列,一直到目前学习的排序算法都是如此。

以下GIF均源自知乎用户:程序员吴师兄

 

看到这个GIF,你肯定能大概理解了,接下来我们一步步分析。

第一趟排序:定义待排序数组R[],i指向无序区的开始位置,以j为扫描指针在当前无序区R[i, n],选择数据中最小的值记为R[k],k记下目前所找到的最小关键字的所在位置,交换R[i]和R[k],至此第一趟排序结束。(初始状态下,R[i], R[j],R[k]均为数组的第一个元素)。

GIF图的初始序列为 4,2,3,6,5,初始状态i,j,k均指向数组的第一个位置。一开始j开始遍历数组,当遍历到第一个元素时,发现2<4,于是k记下当前位置,j继续遍历到数组最后一个元素,没有发现小于k位置的元素,于是交换i和k位置的值,第一趟排序结束。

此时有序区存在1个元素,无序区的元素个数为4。第二趟排序,i指向无序区的第一个元素,也就是数组的第二个元素,j从数组的第二个元素遍历,之后的操作与第一次排序一致。然后进行第三次排序,第四次排序,直到无序区元素个数为0。

选择排序的主要代码实现:

void SelectionSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int k = -1; // The index of the smallest element
    
    for(i=0; i<len; i++)
    {
        k = i;
        
        for(j=i; j<len; j++) // Minimum search
        {
            if( array[j] < array[k] ) // The compare operation is time-consuming  O(n*n)
            {
                k = j;
            }
        }
        
        swap(array, i, k); // swap i k
    }
}

通过分析,我们可以看到算法的时间复杂度为O(n*n)

2、插入排序

当插入第 i (i ≥ 1) 个数据元素时 , 前面的V[0], V[1], … , V[i- - - 1] 。 这 已经排好序 。 这时用 V[i]的关键字与 V[i-1], V[i-2], … 的关键字进行比较 , 找到插入位置即将 V[i]插入 , 原来位置上的对象向后顺移。

插入排序实现的思路就是将所有数据分成两个部分,有序表和无序表。我们要做的就是把无序表中的第一个数据和有序表中的数据一一进行比对(其中有序表中的数据已经是排好序的)。也就是说如果大于有序表中的最后一个数据则无序表第一个元素的位置不变,有序表长度+1,无序表长度-1;如果小于有序表中最后一个元素,则依次对比,选择合适的地方插入。

大家肯定会有疑问,有序表从何而来,其实是我们自己把思路限制住了,一个元素也可以称作有序表。这里我们就把这些数据的第一个元素定义为有序表,其余的定义为无序表。图片源自C语言中文网

第一次比对插入后:

接下来我们通过上面的GIF图片进行具体的分析:

首先令i作为无序表第一个元素的下标,定义temp始终保存i位置的值,一旦需要移动,可以直接覆盖此位置数据,再定义j为有序表的最后一个元素,即j=i-1。

也就是说i首先表示array[1]的下标,可以看出array[j]<array[i],所以保存这个3的数值,将5向后移动一位,然后再将3插入到下标为0的位置。直接上代码,大家结合图片理解一下。

void InertionSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int k = -1;
    int temp = -1;
   
   for(i=1; i<len; i++)
   {
	   k = i;
	   temp = array[k];
	   
	   for(j=i-1; (j>=0)&&(temp<array[j]); j--)
	   {
		   array[j+1] = array[j];
		   k = j;
	   }
	   
		array[k] = temp;  
   }
}

都说插入排序是排序算法里最简单的,怎么我理解起来这么困难,也可能是变量有点多了,搞不清楚了。可以看到这个插入排序的时间算法复杂度也是O(n*n),看上去和选择排序一致,但实际效率要高于选择排序,平均来说,插入排序还是要更好一点,因为有一些比较的操作在不满足条件的时候是不需要做的。

3、冒泡排序

设待排数据元素中的元素个数为n,最多做n-1趟排序,i=1,2,…n-1。第i趟中从后向前,j=n-1,n-2…i,两两比较V[j-1]和V[j]的关键字,如果发生逆序,则交换V[j-1]和V[j]。

之所以称之为冒泡排序法,是因为就像泡泡一样,从最底部一步步跑到最前面,只不过他跑到前面的依据是从最后开始相邻元素对比大小,满足条件后两两交换位置。完成一趟排序,就能把序列中最小的元素放到第一位,

void BubbleSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int exchange = 1;
    
    for(i=0; (i<len) && exchange; i++)
    {
        exchange = 0;
        
        for(j=len-1; j>i; j--)
        {
            if( array[j] < array[j-1] )
            {
                swap(array, j, j-1);
                
                exchange = 1;
            }
        }
    }
}

当没有元素需要交换的时候,就意味着整个排序算法已经结束了,所以这里使用了一个exchange标志,当没有元素需要交换的时候,就不再做无谓的比较,提升效率。我们可以验证,当序列已经排好顺序,则打印i的值为1,值进行了一趟排序,当序列全部逆序的时候,工作量也是最大的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值