題目:
笛卡爾樹:笛卡爾樹中的每個節點有兩個資料域k,a,對於資料域k,滿足二叉搜尋樹性質,對於資料域a,滿足最小堆性質。
給出N個節點,1<=N<=50000,每個節點由一對k,a構成,判斷能否根據這些節點構建一顆笛卡爾樹,如果可以構建則輸出構造出的笛卡爾樹,否則輸出“NO”。
解題思路:
首先,根據N個節點,肯定可以構造出一顆笛卡爾樹。
方法1:
遞迴法 (經過驗證,該方法會逾時) (見代碼1.)
a) 對節點數組,選出a值最小的節點,該節點為根節點
b) 以根節點的k值為樞紐對數組進行劃分,小於k的節點位於根節點左邊,大於k的節點位於根節點右邊
c) 根節點左邊的節點為根節點的左子樹中的節點,右邊的節點為右子樹中的節點,對根節點左邊的節點遞迴構建左子樹,對根節點右邊的節點遞迴構造右子樹。
方法2:
節點逐個插入法,(見代碼2)
a) 對節點數組按k值從小到大排序。
b) 令第一個節點為根節點,從第二個節點開始遍曆數組,將節點逐個插入樹中
將節點插入樹中的步驟為:
從當前待插入節點的前一個節點開始,自底向上找出一個節點,使得該節點的a值比當前節點的a值小。
如果沒找到:
則當前節點為新的根節點,原來的根節點改為當前節點的左孩子。
如果找到:
設找到的節點標號為i,則i節點原來的右孩子改為當前節點的左孩子,當前節點為節點i的右孩子。
c) 插入結束後輸出結果
代碼1:遞迴法
#include<stdio.h>typedef struct{int k,a,num;//num從1開始編號 } Node;Node data[50000];int father[50001];int left_child[50001];int right_child[50001];int n;/*找出data數組中,從下標s開始到下標e之間輔助域a最小的點返回該點在數組中的下標 */int min_node(int s, int e){int min_index,min_a;min_index=s;min_a=data[s].a;while(++s<=e) if(data[s].a < min_a) { min_index=s; min_a=data[s].a; }return min_index; }void swap(Node *a, Node *b){Node temp;temp = *a;*a = *b;*b = temp;}//以index處的元素為樞紐劃分數組,返回樞紐的下標 int partition(int s, int e, int index){int i,j;Node temp,pivot;//交換樞紐元素和開始元素 swap(&data[s], &data[index]);i=s;j=s+1;pivot=data[s];while(j<=e){if(data[j].k < pivot.k) {i++;swap(&data[i], &data[j]);}j++;}swap(&data[s], &data[i]);return i;}/*為data數組下標從s到e的部分建立笛卡爾樹 * s:開始下標, e:結束下標 * child:當前構建的笛卡爾樹的根節點為父節點的孩子 * p: 當前構建的笛卡爾樹的根節點的父節點編號 */void built(int s, int e, int child[], int p){int min_index, pivot_index;if(s>e) return;//找出輔助域a最小的節點,該節點為該子樹的根節點 min_index=min_node(s,e);father[data[min_index].num]=p;child[p] = data[min_index].num;//對數組以根節點的K值為樞紐進行劃分pivot_index = partition(s,e,min_index);//構建左子樹built(s, pivot_index-1, left_child, data[pivot_index].num);//構建右子樹built(pivot_index+1, e, right_child, data[pivot_index].num); }int main(){int i;scanf("%d", &n);for(i=0; i<n; i++){scanf("%d %d", &data[i].k, &data[i].a);data[i].num=i+1;}//構建笛卡爾樹built(0, n-1, left_child, 0); //第3個參數隨意,也可以為right_child//列印結果printf("YES\n");for(i=1; i<=n; i++) printf("%d %d %d\n", father[i], left_child[i], right_child[i]);return 0;}
代碼2:逐個插入法
#include<stdio.h>typedef struct{int k,a,num;//num從1開始編號 } Node;Node data[50000];int father[50001];int left_child[50001];int right_child[50001];int convert[50001];int n;int cmp(const void *a, const void *b){Node *p1,*p2;p1=(Node*)a;p2=(Node*)b;return p1->k - p2->k;}//插入一個節點,index為節點在data數組中的下標 void insert(int index){int i,j;i=data[index-1].num;//找到域a比當前節點小的節點while(i!=0 && data[convert[i]].a > data[index].a) {j=i;i=father[i];}if(i==0){//沒有找到這樣的節點,則當前節點為根節點,前一個節點為當前節點的左孩子 father[j]=data[index].num;left_child[data[index].num] = j;}else{//找到了這樣的節點i,則當前節點為i的右孩子,i節點原來的右孩子改為當前節點的左孩子 left_child[data[index].num]=right_child[i];father[right_child[i]]=data[index].num;right_child[i]=data[index].num;father[data[index].num]=i;}}int main(){int i;scanf("%d", &n);for(i=0; i<n; i++){scanf("%d %d", &data[i].k, &data[i].a);data[i].num=i+1;}//對節點按a從小到大排序qsort(data, n, sizeof(Node), cmp);//對節點依次插入樹中convert[data[0].num]=0; for(i=1; i<n; i++) convert[data[i].num]=i, insert(i);//列印結果printf("YES\n");for(i=1; i<=n; i++) printf("%d %d %d\n", father[i], left_child[i], right_child[i]);return 0;}