本文的结构如下:

  1. 查找(顺序查找、二分查找)
  2. 树的定义
  3. 树的表示

 

在世界中,很多事物的组织方式都是有层次的,比如一个家族,上面有祖父祖母,下面有父亲母亲,然后才是孩子,又比如拿国家来说,中国由很多省份组成,这些省份又由很多市组成。又比如硬盘里的文件,C盘里包含了很多子文件,这些子文件下面又包含许多子文件......这都形成了一种层次关系,这种层次关系就是我们所说的

查找(Searching)

查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录。

静态查找:集合中记录是固定的。//没有插入和删除操作,只有查找。(例:查字典)

动态查找:集合中记录是动态变化的。//除查找,还可能发生插入和删除。

1.顺序查找

一般的表示方法就是把集合放在数组里面,在数组里面查找一个元素,最常见的方法就是顺序查找

顺序查找的基本思想:从表的一端开始,顺序扫描线性表,依次将扫描到的结点关键宇和给定值K相比较。若当前扫描到的结点关键字与K相等,则查找成功;若扫描结束后,仍未找到关键字等于K的结点,则查找失败。

顺序查找的存储结构要求:适用于线性表的顺序存储结构,也适用于线性表的链式存储结构(使用单链表作存储结构时,扫描必须从第一个结点开始)。

//具体算法  
int SeqSearch(Seqlist R,KeyType K)
    { //在顺序表R[1..n]中顺序查找关键字为K的结点,
      //成功时返回找到的结点位置,失败时返回0
      int i;
      R[0].key=K; //设置哨兵
      for(i=n;R[i].key!=K;i--); //从表后往前找
      return i; //若i为0,表示查找失败,否则R[i]是要找的结点
    } //SeqSearch
  
//类型说明
  typedef struct{
    KeyType key;
    InfoType otherinfo; //此类型依赖于应用
   }NodeType;
  typedef NodeType SeqList[n+1]; //0号单元用作哨兵

 

顺序查找算法分析:

  1. 算法中设置哨兵的作用:为了在for循环中省去判定防止下标越界的条件i≥1,从而节省比较的时间。
  2. 成功时的顺序查找的平均查找长度:在等概率情况下,pi=1/n(1≤i≤n),故成功的平均查找长度为(n+…+2+1)/n=(n+1)/2,即查找成功时的平均比较次数约为表长的一半。若K值不在表中,则须进行n+1次比较之后才能确定查找失败。
  3. 时间复杂度为O(n)。

2、二分查找

假设n个数据元素的关键组满足有序(比如:小到大):k1<k2<....<kn,并且是连续存放(数组),那么可以进行二分查找。

搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。   ---维基百科

 1 //javascript版本
 2 Array.prototype.binary_search = function(low, high, khey) {
 3     if (low > high)
 4         return -1;
 5     var mid = parseInt((high + low) / 2);
 6     if (this[mid] > khey)
 7         return this.binary_search(low, mid - 1, khey);
 8     if (this[mid] < khey)
 9         return this.binary_search(mid + 1, high, khey);
10     return mid;
11 };

 

/*C版本*/
int
BinarySearch(List Tbl , ElementType K) { /*在表Tbl中查找关键字为诶K的数据元素*/ int left,right,mid,NoFound = -1; left = 1; /*初始左边界*/ right = Tbl->Length; /*初始右边界*/ while (left <= right) { mid = (left+right)/2; /*计算中间元素坐标*/ if(K < Tbl->Element[mid]) right = mid -1; else if (K>Tbl->Element[mid]) left = mid + 1; else return mid; /*查找成功,返回数据元素*/ } return NotFound; /*查找不成功,返回-1*/ }

 

二分查找的时间复杂度为 O(logn)。

由二分查找,我们可以得出这样的启示:(ASL是平均查找次数)

 

树的定义

树:n(n>=0)个结点构成的有限集合。当n=0时,称为空树;对于任意一颗非空树(n>0),它具有以下性质:

  1. 树中有一个成为“根(Root)”的特殊结点,用r表示;
  2. 其余结点可氛围m(m>0)个互不相交的有限集T1,T2,...Tm,其中每个集合本森又是一棵树,成为原来树的“子树(SubTree)”

 在(a)中,A就是这棵树的根,(b)、(c)、(d)、(e)皆为(a)的子树。在(b)中,B又为这个子树的根....通过观察以上树的结构,可以得出以下几点:

  1. 子树是不相交的;
  2. 除根节点外,每个结点有且只有一个父节点;
  3. 一棵N个节点的树,有N-1条边;

关于树的一些基本术语:

  1. 结点的度(Degree):结点的子树个数
  2. 树的度:树的所有结点中最大的度数
  3. 叶节点(Leaf):度为0的结点
  4. 父节点(Parent):有子树的结点是其子树的根节点的父节点
  5. 子节点(Child):若A结点是B结点的父节点,则称B结点是A的子节点,子节点也称孩子节点。
  6. 兄弟结点(Sibling):具有同一父节点的各结点彼此是兄弟结点。
  7. 路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2....,nk,ni是ni+1的父节点,路径所包含边的个数为路径的长度
  8. 祖先结点(Ancestor):沿树根到某一节点路径上的所有结点都是这个结点的祖先结点;
  9. 子孙结点(Descendant):某个结点的子树中的所有结点是这个结点的子孙。
  10. 结点的层次(Level):规定根节点在1层,其他任一结点的层数是其父节点的层数加1.
  11. 树的深度(Depth):树中所有结点中的最大层次是这棵树的深度。

树的表示

 所谓树可否用数组或者链表实现呢?



用数组实现树难度是比较大的。因为用数组表示我们只看到了结点的顺序,很难判别一个结点的父亲是谁?儿子是谁。



上图是利用链表实现树,A的指针指向它的三个儿子。像这样一个结构,我们可以看到很多结点都有不同数量的指针,给程序的设计带来了困难。有一种方法就是我们可以给各个结点都设置同样数量的指针,这样方法的好处就是树的各个结点结构是统一的。程序处理起来比较方便。但是这样带来的问题就是,如果树有N个结点,每个结点有三个指针域,所以整个树就会有3n个指针域,而我们的树只有N-1个指针是非0的,这样会造成有2n+1个指针域是空的,就会造成空间的浪费。有没有更好的方法呢?

有一种方法叫做“儿子-兄弟表示法”。



左边指针指向这个结点的第一个儿子,右边指针指向下一个兄弟。这样树中每个结点的结构是统一的,都有两个指针域,同时空间浪费也不大。

把这个树顺时针旋转45度(如下图所示),这个时候我们看到的是一棵树,树的特点就是每个结点都有两个指针,每个结点最多有两个儿子。这种树我们叫做二叉树。二叉树就是度为2的一种树。

 

posted @ 2017-11-04 10:10  珵城  阅读(162)  评论(0)    收藏  举报