其他分享
首页 > 其他分享> > No.7.1 树的初识

No.7.1 树的初识

作者:互联网

一、树的初识

1. 树:不包含回路的、无向、连通图! <=>  于是树有许多特性:

  1.一棵树中,任意两个节点有且仅有唯一的一条路径连通;

  2.一棵树如果有 n 个节点,那么它一定恰好有 n-1 条边;

  3.在一棵树中加一条边,将会构成回路!

 

2. 定义:

  根节点:只有一个,它没有父节点;

  叶节点:没有子节点的节点;

  深度:从根到某个节点的层数,其中根节点为第一层,depth=1;

  二叉树:每个节点最多有两个子节点,即二叉树要么为空,要么由根节点、左子树、右子树组成,左右子树也是一颗二叉树(可以为空);

  满二叉树:每个内部节点都有两个子节点的二叉树,成为满二叉树  =》 所有的叶节点深度相同! =》 深度为 h 的满二叉树有 2**n - 1 个节点(等比数列求和~_~)!

  完全二叉树:若二叉树深度=h,上h-1层为满二叉树,h 层最右边缺若干节点,这样的成为完全二叉树!(如果一个节点有右子节点,那么一定也有左子节点)

 

3.完全二叉树的特点:

  1.如果完全二叉树的一个父节点编号为k,则子节点的编号为 2*k, 2*k+1;同理,根据子节点也可以推出父节点编号:子 k -> 父 k/2 (向下取整)

  2.如果完全二叉树有N个节点,则其深度 depth = logN;

  3.最后一个非叶节点是第 n/2 个节点(节点总数为 n);以此类推,再上一层的最后一个节点是第 n/4 个节点。。。

 

二、堆 【前提是一颗完全二叉树】-- 神奇的优先队列

所有父节点 < 子节点的,称为最小堆;反之,称为最大堆。

EG. 定一个一维数组,转换为堆,并进行排序、写入操作。

/* No.1 利用最小堆,实现队列的从小到大排列
int h[100];
int n;

void swap(int x,int y){    //此处是为了交换树中的父子节点
  int t;
  t=h[x];
  h[x]=h[y];
  h[y]=t;
}

void siftdown(int i){   //最小堆,min=h[1]
  int t,flag=0;
  //两个亮点:
  //1.t=i*2; i=t; => while i<=n/2, 自我递归:即上层的节点变动,会继续下沉,直到叶节点
  //2.指针 t 实现了两个目的:验证该子树是否已经是最小堆,如果不是,父节点与其子节点中的较小值交换
  while(i*2<=n && flag==0){

    if(h[i]>h[2*i]) //先比较左子树
      t=i*2;
    else
      t=i;

    if(i*2+1 <= n && h[t]>h[i*2+1]) //再检测是否存在右子树,如果存在,则用指针 t 与其比较,这样可以实现父节点总是与左右子节点中的较小值交换
      t=i*2+1;

    if(t!=i) //如果不是最小堆,指针 t 下移;否则,不需再继续递归
    {
      swap(t,i);
      i=t;   //继续递归
    }
    else
      flag=1;  //说明已经是最小堆,不需再检测
  }
}

void create(){  //将一维数组转化为堆
  int i;
  for(i=n/2;i>=1;i--){ //满二叉树的最后一个非叶子节点的索引坐标是 n/2,所有的叶子节点肯定都是满足最小堆的
    siftdown(i);
  }
}

int deletemax(){   //从小到大排序用最小堆,时间复杂度O(NlogN)
  int t;
  t=h[1];        //记录最小堆的最小值 h[1]
  h[1]=h[n];   //把堆的最后一个值推到堆顶,并将 h[1] 出栈
  n--;
  siftdown(1);  //再次下沉最小堆,找到新的最小值
  return t;
}

int main(){
  int i,num;
  scanf("%d",&n);
  for(i=1;i<=n;i++)
    scanf("%d",&h[i]);

  num=n;
  create();

  for(i=1;i<=num;i++)  //为何用中间变量num? 因为deletemax中,n--
    printf("%d ",deletemax());

  getchar();getchar();return 0;
}
*/

/* No.2 利用最小堆,实现队列从小到大排列或者从大到小排列
int h[100];
int n;

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

void siftdown(int i){
  int t,flag=0;
  while(i*2<=n && flag==0){

    if(h[i]>h[2*i]) //先比较左子树
      t=i*2;
    else
      t=i;

    if(i*2+1 <= n && h[t]>h[i*2+1]) 

      t=i*2+1;

    if(t!=i){
      swap(t,i);
      i=t;
    }
    else
      flag=1;
  }
}

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

void heapsort(){    // 最小值h[1] 与 h[n] 交换,然后把 h[n] 出队,剩余的 n-1 个点,继续下沉,寻找另一个最小值,循环
  while(n>1){
    swap(1,n);
    n--;
    siftdown(1);
  }
}

int main(){
  int i,num;
  scanf("%d",&n);
  for(i=1;i<=n;i++)
    scanf("%d",&h[i]);

  num=n;
  create();
  heapsort();
  for(i=num;i>=1;i--)  //倒序,从小到大排列
    printf("%d ",h[i]);

  printf("\n");
  for(i=1;i<=num;i++)  //顺序,从大到小排列
    printf("%d ",h[i]);

  getchar();getchar();return 0;
}
*/

 

三、堆总结:

1. 像堆这样支持元素写入和寻找最大(小)值的数据结构成为优先队列,比普通队列的入栈、枚举,时间复杂度低O(NlogN)。

2. Dijkstra算法时,需要找到确定点的下一个距离最近的点,也可以使用这里的堆优化算法的最小堆,使得算法复杂度降低到O((M+N)logN)。

3. 求一个数列中的第K大的数:

  建立一个大小为K的最小堆,将队列中的值与堆顶比较:小于堆顶者,直接丢弃;大于堆顶者,替换堆顶,并执行最小堆维护;循环。。。最终的堆顶值,既是第K大的数。

  同理,求数列中的第K小的数,用最大堆!

标签:int,void,最小,No.7,--,初识,二叉树,节点
来源: https://www.cnblogs.com/yalimy/p/15098539.html