堆 及 堆排序

定義: 一種特別的樹狀數據結構.
一個堆滿足的特性:

  1. 給定堆中任意節點P和C,若P是C的母節點,那麼P的值會小於等於(或大於等於)C的值”。若母節點的值恆小於等於子節點的值,此堆稱爲最小堆(min heap)
  2. 若母節點的值恆大於等於子節點的值,此堆稱爲最大堆(max heap)
堆的實現

堆的實現通過構造二叉堆(binary heap),實爲二叉樹的一種

二叉樹:是每個節點最多隻有兩個分支(即不存在分支度大於2的節點)的樹結構.

而堆的實現是靠二叉樹中的 完全二叉樹實現的.

完全二叉樹:對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹.

完全二叉樹 如下圖 都是完全二叉樹:

                     

非完全二叉樹

在這裏插入圖片描述

即:完全二叉樹滿足 每一層都是連續集中在左側;

此處用數組 來存儲完全二叉樹.

根節點的下表爲 1.
若有一個結點 i, (假設 i 結點有父節點且 有 左子樹和右子樹),則 其父節點的下標爲 i/2,左子樹 的 下標爲 2*i右子樹的下表爲 i*2+1.

在這裏插入圖片描述
有兩種 增加結點的方法.
1.從根結點( 1 )開始,然後 2, 3 . 每次增加一個結點後向上調整.
2. 先將總結點數的 一半 加在最底層(從左往右),然後 開始從倒數第二層開始,每次增加一個結點都向下調整.

以大根堆 爲例:

這裏就涉及二叉樹的一些性質了.
如果 i=1i = 1,則結點 i 是二叉樹的根,無雙親; 如果 i > 1,則其雙親爲 i/2⌊i/2⌋.
如果 2i>n2*i > n,則結點 i 無左孩子(結點 i 爲葉子結點);否則其左孩子是結點 2i2*i
如果 2i+1>n2*i + 1 > n,則結點 i 無右孩子,否則其右孩子是結點 2i+12*i + 1

向上調整:

每次調整前先判斷 這個結點是否爲 根結點 即.

		if(i == 1)  return ;  // i 爲 待調整的點

若不是根節點 則判斷 該結點 與 該節點的父節點的大小,若 該結點大,則 該節點於父節點互換位置,繼續向上 判斷,直到 該節點 是父節點 或 該節點沒有 父結點 大 時退出.

       while(i != 1)
        {
                if(h[i] < h[i/2])  swap(i,i/2);    
                else  break;
                
                i = i/2;
        }

向下調整:

假設 待插入的結點爲 i.
每次 需要先判斷 i結點是否存在 左子樹,若存在 則進行判斷,不存在則退出.

用 變量 temp 備份當前 最大 的結點.

先 判斷 i 和 左兒子 的大小, 有temp 備份最大值.
判斷 i 是否有 右兒子,若有 則用 temp 與 i 的 右兒子 比較,用 t 備份最大值.
若沒有 右兒子則不用進行判斷

然後 判斷 i與 temp 是否相等. 若 (i == temp) 則代表 當前 i 是最小的 值,退出.
否則 交換 temp 與 i,繼續進行判斷

        while(i * 2 <= n)
        {       
                //判斷和左兒子的關係 
                if(h[i] < h[i*2])  t = i*2;
                else temp = i;
                
                //如果有 右兒子,
                if(i*2+1 <= n)
                {       
                        if(h[temp] < h[i*2+1])    t = i*2+1;
                }
                
                if(temp != i)
                {       
                        swap(temp,i);
                        i = temp;  
                }
                else break;
        }

到此,已經 建立了一個滿二叉樹. 之後需要做的 每次將 根拿出來,然後將 二叉樹的 最後一個結點 放到 根的 位置,然後讓根 向下調整.

此處,將 根節點 拿出放到 數組的最後一個位置,待 排序後, 即爲 從小向大 排序.

void heapsort()
{
        while(n > 1)
        {
                swap(1,n);
                n--;
                siftdown(1);
        }

        return ;

}

假設 堆 如下圖所示:
在這裏插入圖片描述
第一次 排序後 :
在這裏插入圖片描述
然後向下調整. 直到最後只剩下 根節點時結束.

#include<stdio.h>

// 用數組 模擬二叉樹
int h[101];   //用來存放堆的數組
int n;  //存儲 堆 中的元素個數

void swap(int x,int y)
{
	int temp = h[x];
	h[x] = h[y];
	h[y] = temp;
	
	return ;
}

void siftdown(int i)   //向下調整
{
	int t;  //記錄當前最大值
	int flag = 0;   //判斷是否需要繼續向下調整

	while(i * 2 <= n)
//	while(i * 2 <= n && flag == 0)
	{
		//判斷和左兒子的關係
		if(h[i] < h[i*2])  t = i*2;
		else t = i;

		//如果有 右兒子,
		if(i*2+1 <= n)
		{
			if(h[t] < h[i*2+1])    t = i*2+1;
		}

		if(t != i)
		{
			swap(t,i);
			i = t;   //更新 i 爲剛纔與它交換的兒子結點的編號,便於向下調整
		}
		else break;
		/*else
		{
			flag = 1;  //否則 說明當前的父節點 已經比兩個子節點都要大了,不需要繼續進行調整
		}*/
	}

	return ;
}

/*void siftup(int i)  //向上調整
{
	int flag = 0;
	if(i == 1)  return ;   //已經是 根結點了
	while(i != 1 && flag == 0)
	{
		//判斷是否比父節點 小
		if(h[i] < h[i/2])  swap(i,i/2);
		else  flag = 1;

		i = i/2;
	}
	return ;
}
*/

void heapsort()
{
	while(n > 1)
	{
		swap(1,n);
		n--;
		siftdown(1);
	}

	return ;

}

void creat()
{
	int i;
	for(i = n/2;i >= 1;i--)   siftdown(i);
	return ;
}

int main()
{
	int num,i;
	scanf("%d",&num);
	n = num;
	for(i = 1;i <= num;i++)   scanf( "%d",&h[i]);
	
	creat();  //建堆    大頂堆   
	
	//堆排序
//	heapsort();

	for(i = 1;i <= num;i++)  printf( "%d ",h[i]);
	

	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章