排序算法思想及代码

堆排序,顾名思义,就是基于堆。因此先来介绍一下堆的概念。
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
或者说,堆排序将所有的待排序数据分为两部分,无序区和有序区。无序区也就是前面的最大堆数据,有序区是每次将堆顶元素放到最后排列而成的序列。每一次堆排序过程都是有序区元素个数增加,无序区元素个数减少的过程。当无序区元素个数为1时,堆排序就完成了。
本质上讲,堆排序是一种选择排序,每次都选择堆中最大的元素进行排序。只不过堆排序选择元素的方法更为先进,时间复杂度更低,效率更高。
图例说明一下:(图片来自http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html

具体步骤如下:

1 首先从第一个非叶子节点开始,比较当前节点和其孩子节点,将最大的元素放在当前节点,交换当前节点和最大节点元素。

2 将当前元素前面所有的元素都进行1的过程,这样就生成了最大堆

3 将堆顶元素和最后一个元素交换,列表长度减1。由此无序区减1,有序区加1

4 剩余元素重新调整建堆

5 继续34,直到所有元素都完成排序

 

int adjust_heap(vector<int> &v,int length, int i){

       int left = 2 * i;

       int right = 2 * i + 1;

       int largest = i;

       int temp;

 

       while(left < length || right < length){

                if (left < length &&v[largest] < v[left]){

                        largest = left;

                }

                if (right < length&& v[largest] < v[right]){

                        largest = right;

                }

 

                if (i != largest){

                        temp = v[largest];

                        v[largest] = v[i];

                        v[i] = temp;

                        i = largest;

                        left = 2 * i;

                        right = 2 * i + 1;

                }

                else{

                        break;

                }

       }

}

 

int build_heap(vector<int> &v,int length){

       int i;

       int begin = length/2 - 1; //get the last parent node

       for (i = begin; i>=0; i--){

                adjust_heap(v,length,i);

       }

}

 

int heap_sort(vector<int> &v){

       int length = v.size();

       int temp;

        printline("before sort:",v);

       build_heap(v,length);

       while(length > 1){

                temp = v[length-1];

                v[length-1] = v[0];

                v[0] = temp;

                length--;

                adjust_heap(v,length,0);

       }

       printline("after sort:",v);

}

 

 

插入排序的思想有点像打扑克抓牌的时候,我们插入扑克牌的做法。想象一下,抓牌时,我们都是把抓到的牌按顺序放在手中。因此每抓一张新牌,我们都将其插入到已有的排好序的手牌当中,注意体会刚才的那句话。也就是说,插入排序的思想是,将新来的元素按顺序放入一个已有的有序序列当中。

举个例子可能更容易理解一些,假设有这样一系列数字:

2 4 9 3 6  首先我们考虑数字2,假设后面的数字不存在(手中只有一张8,又抓来了2),那么显然2应该放在8的前面。

2 8 4 9 3 6  又抓来了一张4,现在大家都知道应该怎么办了吧?

2 4 8 9 3 6  又来了个9,没错,正好不用换顺序

2 4 8 9 3 同样的道理,考虑3该放的位置,显然放在24的中间

2 3 4 8 9 6  最后一个也是一样,最后得到从小到大的序列

2 3 4 6 8 9  完成排序

printline("before sort:", v);

       for (int i=1; i<v.size(); i++){

                int key = v[i];

                int j = i-1;

                while (j >= 0 &&v[j] > key){

                        v[j+1] = v[j];

                        j--;

                }

                v[j+1] = key;

 

       }

       printline("after sort:", v);

 

思想

同之前介绍的两种排序方式一样,冒泡排序也是最简单最基本的排序方法之一。冒泡排序的思想很简单,就是以此比较相邻的元素大小,将小的前移,大的后移,就像水中的气泡一样,最小的元素经过几次移动,会最终浮到水面上。

举例分析说明一下,如下数据:

2 7 4 6 9 1 首先比较最后两个数字,发现19小,于是前移

2 7 4 6 1 9 然后比较61

2 7 4 1 6 9 继续前移,然后是41

7 1 4 6 9 71比较

2 1 7 4 6 9 21

1 2 7 4 6 9 至此,第一趟冒泡过程完成,最小的元素1被移到第一个,不再参与后面的排序过程。下一趟冒泡过程同理,比较69,以此类推,最终得到结果。

cout << "bubble sort:"<< endl;

       printline("before sort:", v);

       for (int i=0; i<v.size(); i++){

                int temp = 0;

                for(int j=v.size()-1; j>0;j--){

                        if (v[j] < v[j-1]){

                                temp = v[j];

                                v[j] = v[j-1];

                                v[j-1] = temp;

                        }

                }

       }

       printline("after sort:",v);

 

分析

因为每一趟排序都使有序区增加了一个气泡,在经过n-1趟排序之后,有序区中就有n-1个气泡,而无序区中气泡的重量总是大于等于有序区中气泡的重量,所以整个冒泡排序过程至多需要进行n-1趟排序。以此本算法的时间复杂度还是O(n*n),也不能算是一个高效的算法。

细心分析不难发现,本算法还有可以优化的空间,若在某一趟排序中未发现气泡位置的交换,则说明待排序的无序区中所有气泡均满足轻者在上,重者在下的原则,因此,冒泡排序过程可在此趟排序后终止。为此,在下面给出的算法中,引入一个布尔量exchange,在每趟排序开始前,先将其置为FALSE。若排序过程中发生了交换,则将其置为TRUE。各趟排序结束时检查exchange,若未曾发生过交换则终止算法,不再进行下一趟排序。这样可以减少不必要的比较。代码如下

int bubble_sort(vector<int> &v){

       cout << "bubble sort:" << endl;

       printline("before sort:", v);

       bool exchange;

       for (int i=0; i<v.size(); i++){

                int temp = 0;

                exchange = false;

                for(int j=v.size()-1; j>0;j--){

                        if (v[j] < v[j-1]){

                                temp = v[j];

                                v[j] = v[j-1];

                                v[j-1] = temp;

                                exchange =true;

                        }

                }

                if (!exchange){

                        break;

                }

       }

       printline("after sort:",v);

}

 

思想

快速排序采用的思想是分治思想。

快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。

举例说明一下吧,这个可能不是太好理解。假设要排序的序列为

2 2 4 9 3 6 7 1 5 首先用2当作基准,使用i j两个指针分别从两边进行扫描,把比2小的元素和比2大的元素分开。首先比较2552大,j左移

2 2 4 9 3 6 7 1 5 比较211小于2,所以把1放在2的位置

2 4 9 3 6 7 1 5 比较244大于2,因此将4移动到后面

2 4 9 3 6 7 4 5 比较27262329,全部大于2,满足条件,因此不变

经过第一轮的快速排序,元素变为下面的样子

[1] 2 [4 9 3 6 7 5]

之后,在把2左边的元素进行快排,由于只有一个元素,因此快排结束。右边进行快排,递归进行,最终生成最后的结果。

int quicksort(vector<int> &v, intleft, int right){

       if(left < right){

                int key = v[left];

                int low = left;

                int high = right;

                while(low < high){

                        while(low < high&& v[high] > key){

                                high--;

                        }

                        v[low] = v[high];

                       while(low <high && v[low] < key){

                                low++;

                        }

                        v[high] = v[low];

                }

                v[low] = key;

                quicksort(v,left,low-1);

               quicksort(v,low+1,right);

       }

}

 

今天来说一个简单的需求:在一个序列中找到第二大的元素。

一眼看到这个问题,感觉解决的方法有很多,因为这并不是一个困难的问题。随便一想,能有下面几种解法:

1 首先排序,然后取第二个位置的元素

2 循环遍历元素序列,找到最大的元素,然后将其移除。再重复此过程,得到第二大的元素

当然还有其他的思路,这里就不一一列举了。如果大家有什么好的想法,可以给我留言,咱们一起探讨。

 

仔细分析一下,不难发现,上面的方法虽然可以达到目的,但是效率都不高。第一种方法相当于一次排序过程,最快也要O(nlogn)的时间才能完成。而第二种方法需要循环遍历序列两次,O(n)+O(n)的时间复杂度虽然不是无法接受,但毕竟还是要循环两次。对于我们写软件的人来说,显然希望代码是完美的。因此在这里,提出一个只循环一次的方法,供大家借鉴参考。如果大家有好方法,欢迎提出。

废话不说,下面介绍算法思路:

我们既然可以循环遍历一次得到最大的元素,为什么不能保存住第二大的元素呢?当然可以,我们在比较元素大小时,只要把小的保存起来,经过一遍循环,这个元素就是第二大的元素了

代码就更简单了

int find_second_biggest(vector<int>&v){

       int len = v.size();

       int max,second;

       if (len < 2){

                return -1;

       }

       if (v[0]>v[1]){

                second = v[1];

                max = v[0];

       }

       else{

                second = v[0];

                max = v[1];

       }

       for (int i=2; i< len; i++){

                if(max < v[i]){

                        second = max;

                        max = v[i];

                }

                else if (second < v[i]){

                        second = v[i];

                }

       }

       return second;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值