1. 程式人生 > >演算法學習之一:堆排序

演算法學習之一:堆排序

堆的屬性:

1. 它是一顆完全的二叉樹

2. 每個結點大於或等於她的任意一個孩子。

堆排序:

堆排序:使用的是二叉堆,它是一顆完全的二叉樹。
如何將一個ArrayList中儲存的資料用堆標示?
排列順序如下:樹根在位置0處(0是索引),它的2個孩子就在索引1和2處,類推==》對於位置在i處的結點,它的左孩子在2i+1處,右孩子在2i+2處,父親在(i-1)/2處。
好了,理論知識具備了,我們就來舉例子看看一個數組如何表示成堆:

這裡寫圖片描述

先來推導第一層:a是第一個元素:索引 i=0,是根節點,左孩子(2i+1),索引是2*0+1=1,對應元素是b; 右孩子是(2i+2),索引是2*0+2=2,對應元素是c.
再來推導第二層: b做為父節點,i=1,左孩子(2i+1),索引是2*1+1=3,對應元素是d,右孩子是(2i+2),索引是2*1+2=4,對應元素是e.
……….
依此類推,就形成了一顆二叉樹。

好,再來一個例子試試:
給定一個ArrayList, 資料是:62, 42, 59, 32, 39, 44, 13, 22, 29, 14, 33, 30, 17, 9
這個二叉樹元素是怎麼擺放的呢?

這裡寫圖片描述

好了,ArrayList用堆表示就講得差不多了,下面再來講新增和刪除結點。

1.新增一個新結點:(大家記住是從樹的末尾新增,並且要保證根永遠是最大的)

舉例子:一個集合的資料:3, 5, 1, 19 ,11, 22 ,如何依次放入?
首先堆是空的,先放3, 3就作為根節點。來個元素5,5就作為3的左孩子放置,放置好之後,拿5和它的父結點3比較,5大,調換位置,5在根節點,3跑到左孩子位置。再來一個元素1,結點末尾放置,然後拿1和父結點5比較,小於父結點,位置不變。再來一個元素19, 19也是放在末尾位置,然後也和父結點比較…….

這裡寫圖片描述

按照以上方法插入,保證根元素最大。為什麼這樣做呢?因為當刪除的時候,是從根結點刪除,這樣刪除拿到的每個元素就是按照從大到小排列的。並且,堆在插入鍵值和刪除根元素時,執行效率很高。

2.再來看刪除根結點:刪除根結點之後,末尾元素跑到根結點處,再依次比較,再重構這顆樹,以保持堆的特徵。

先看下怎麼刪除堆中的一個元素?

這裡寫圖片描述

1.從根元素62刪除,將末尾元素9放在根結點處,然後拿9和左右孩子比較,哪個孩子大,哪個孩子就放在根結點處,調整順序,保證根結點最大
2. 9現在在59的位置了,這時候,繼續和左右孩子比較,如果順序需要調整,就繼續調整。一直比較到沒有孩子或是值都大於孩子,就不比較了。真樣做的目的是保證每個父節點是大於左右孩子的。
3 如果還要刪除元素,重複1和2的步驟即可。

拿圖演示:刪除62

這裡寫圖片描述

現在再來刪除59,看看演示過程:

這裡寫圖片描述

以上就是對排序的過程。

堆的具體實現:

現在設計一個類,來實現堆排序演算法。
所有有疑惑的地方或是這一步為什麼這麼做,都已經在程式碼中詳細註釋了,我就不單獨再寫文字說明。

public class Heap {

    private ArrayList<Integer> list = new ArrayList<>();

    public Heap(int[] elements){
        for(int i = 0;i<elements.length;i++){
            addElemet(elements[i]);
        }
    }

    /**
     * 往堆中新增一個元素:從末尾新增,依次和父結點比較大小,保證根元素最大
     * @param newElement
     */
    public void addElemet(int newElement) {
        list.add(newElement);//往根節點新增
        int currentIndex = list.size()-1;//預設最後一個元素的索引
        //比較最後新增的元素和父結點的大小,直到到跟結點為止:
        while(currentIndex>0){
            int parentIndex = (currentIndex-1)/2;//父索引的位置
            if(list.get(currentIndex)>list.get(parentIndex)){
                //孩子比父親要大,需要調整順序
                int temp = list.get(parentIndex);
                list.set(parentIndex,list.get(currentIndex));
                list.set(currentIndex,temp);
                currentIndex = parentIndex;//指標也要向上挪動一步
            }else{
                break;
            }
        }
    }

    /**
     * 刪除結點,先從根結點刪除,然後調整順序
     * @return: 返回刪除的是哪個元素
     */
    public int removeElemet(){
        if(list.size()==0) return -Integer.MAX_VALUE;// 如果堆為空,返回一個最大的負整數
        int removeElement = list.get(0);//要刪除的根元素
        list.set(0,list.get(list.size()-1));//把最後一個元素放在根元素位置
        list.remove(list.size()-1);//最後一個元素要刪除掉
        //然後比較根元素和孩子的大小並做相應調整:
        int currentIndex = 0;
        //一直要比較到最後一個元素
        while(currentIndex<=list.size()-1){
            int lefeChildIndex = 2*currentIndex+1;
            int rightChilIndex = 2*currentIndex+2;
            if(lefeChildIndex>=list.size()) break;//刪掉根結點之後,只剩下一個,不需要再去比較
            int maxIndex = lefeChildIndex;//把左孩子當作最大的
            if(rightChilIndex<list.size()){//右邊孩子比較大,將最大指標指向右孩子
                if(list.get(maxIndex)<list.get(rightChilIndex)){
                    maxIndex = rightChilIndex;
                }
            }
            //比較父結點和maxIndex大小
            if(list.get(currentIndex)<list.get(maxIndex)){
                int temp = list.get(currentIndex);
                list.set(currentIndex,list.get(maxIndex));
                list.set(maxIndex,temp);
                //指標往下移動:
                currentIndex = maxIndex;
            }else{
                break;
            }

        }
        return removeElement;
    }

    public int size(){
        return list.size();
    }
}

再來一個測試類:(直接就在android一個activity中做的測試,沒有去單獨建java工程)

private TextView mTv;
    int[] list = {2,15,12,5,8,55,33,62,7,10};
    private Heap  heap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main5);
        mTv = (TextView) findViewById(R.id.tv_show1);
        heap = new Heap(list);
        mTv.setText(getData());

    }

    private String getData() {
        StringBuilder sb = new StringBuilder("");
        for(int i =0;i<list.length;i++){
            int ele = heap.removeElemet();
            sb.append(ele+"---");
        }
        return  sb.toString();
    }

可以看到列印結果是:62—55—33—15—12—10—8—7—5—2

以上就是整個堆的排序過程的學習,至於堆的時間複雜度,這裡沒有講,後面如果有時間的話就補充,沒有的話,大家可以在網上查詢資料。

參考資料:java程式設計基礎(進階篇)。