Qi

Cogito ergo sum

树由根节点(root)和若干子树(T1,T2, … Tm-1)构成的具有层次关系的数据结构。

相关术语

节点的度 : 该节点子树个数,度为0的节点为叶子节点,度不为0的节点为分支节点

树的度 : 树中的节点的度最大值为树的度

节点的层次 : 根为第一层,往下依次递增

树的深度 : 节点层次最大值为树的深度

森林 : n棵不相交的树的集合为森林

二叉树

常用于实现二叉查找树、二叉堆

  • 每个节点至多有两颗子树(树的度<=2),子树分左右

  • 第i层至多有2i-1个节点,深度为k的二叉树,最多节点数2^k-1个节点(具有最多节点的称作满二叉树),其中每个节点都与深度k满二叉树中1-n节点对应,则为完全二叉树

满二叉树

完全二叉树

二叉排序树

  • 左子树所有节点值小于根节点,右子树所有节点值大于根节点值
  • 左右子树都是二叉排序树

二叉平衡树 AVL

  • 左右子树深度差不超过1
  • 左右子树都是二叉平衡树

哈夫曼树

最优二叉树

代码实现

哈夫曼编码

解决报文编码的问题,将字符串转换为唯一的二进制码,且二进制码的长度最小

每个字符在字符串中出现频率为W,其编码长度为L,编码字符n个,则编码后二进制码的总长度为W1L1+W2L2+…+WnLn,利用哈夫曼树特性求总长最小编码

B树

平衡的多路查找树

B树的阶:所有孩子节点树的最大值(m叉树:每个节点最多m棵子树)

分支节点最少有两颗子树

分支节点包含信息(n,A0,K1,A1,K2,A2,…,Kn,An),其中,n为结点中的关键字树,A为指向子树根结点的指针,K为关键字,且Ai-1所指子树中所有结点的关键字均小于Ki,An所指子树中所有结点的关键字均大于Kn;

所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空

B+树

B+树是应文件系统所需而出的一种B-树的变型树。一棵m阶B+树和m阶的B-树的差异在于

  1. 有n棵子树的结点中含有n个关键字,每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点;

  2. 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接

红黑树

红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的。树中每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子节点或父节点,则该结点相应的指针属性值为NIL,我们可以把这些NIL视为指向二叉搜索树的叶节点(外部结点)的指针,而把带关键字的结点视为树的内部结点。

性质

  1. 每个结点或是红色的,或是黑色的
  2. 根结点是黑色的
  3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的
  5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  6. 虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)

键树

如果一个关键字可以表示成字符的序号,即字符串,那么可以用键树(keyword tree),又称数字搜索树或字符树,来表示这样的字符串的集合。键树的结构受启发于一部大型字典的“书边标目”。字典中标出首字母是 A,B,C,…,Z的单词所在页,再对各部分标出第二字母为A,B,C,…,Z的单词所在的页等等。
键树是一种特殊的查找树,它是一棵度大于等于2的树,树中的每个节点不是包含一个或几个关键字,而是只含有组成关键字的符号。
比如:如果关键字是数值,则节点中只包含一个数位;如果关键字是单词,则节点中只包含一个字母字符。根结点不代表任何字符,根以下第一层的结点对应于字符串的第一个字符,第二层的结点对应于字符串的第二个字符……每个字符串可由一个特殊的字符如“$”等作为字符串的结束符,用一个叶子结点来表示该特殊字符。把从根到叶子的路径上,所有结点(除根以外)对应的字符连接起来,就得到一个字符串。因此,每个叶子结点对应一个关键字。在叶子结点还可以包含一个指针,指向该关键字所对应的元素。整个字符串集合中的字符串的数目等于叶子结点的数目。如果一个集合中的关键字都具有这样的字符串特性,那么,该关键字集合就可采用这样一棵键树来表示。

键树的存储通常有两种方式:

  1. 用树的孩子兄弟链来表示键树,称为双链树;每个Node有三个域:symbol域:存储关键字的一个字符;son域:存储指向第一棵子树的根的指针;brother域:存储指向右兄弟的指针。
  2. 用多重链表来表示键树,称为Trie树或字典树。

字典树

如果以树的多重链表来表示键树,则树的每个结点应包含d个(d为关键字符的基,如:字符集由英文大写字母构成时,则d=26)指针域,此时的键树又称为字典树。
字典树典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 
Tire树的三个基本性质:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符;
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
  3. 每个节点的所有子节点包含的字符都不相同。
    Tire树的应用:
  4. 串的快速检索
    给出N个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。在这道题中,我们可以用数组枚举,用哈希,用字典树,先把熟词建一棵树,然后读入文章进行比较,这种方法效率是比较高的。
  5. “串”排序
    给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出。用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。
  6. 最长公共前缀

后缀树

所谓后缀树,就是包含一则字符串所有后缀的压缩了的字典树。先说说后缀的定义。给定一长度为n的字符串S=S1S2..Si..Sn,和整数i,1 <= i <= n,子串SiSi+1…Sn都是字符串S的后缀。以字符串S=XMADAMYX为例,它的长度为8,所以S[1..8],S[2..8], … , S[8..8]都算S的后缀,我们一般还把空字串也算成后缀。这样,我们一共有如下后缀。对于后缀S[i..n],我们说这项后缀起始于i。
键树只适合前缀匹配和全字匹配,并不适合后缀和子串匹配,而后缀树在这方面则非常合适。它与键树的最大不同在于,后缀树的单词集合是由指定字符串的后缀子串构成。

区间树与线段树

区间树是在红黑树基础上进行扩展得到的支持以区间为元素的动态集合的操作,其中每个节点的关键值是区间的左端点。通过建立这种特定的结构,可是使区间的元素的查找和插入都可以在O(lgn)的时间内完成。相比于基础的红黑树数据结构,增加了一个max[x],即以x为根的子树中所有区间的端点的最大值。
线段树是一种平衡二叉查找树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。主要的处理思想是基于分治的思想。
设根节点的区间为[a,b),区间长度为L = b - a,线段树的性质:
1)线段树是一个平衡树,树的高度为log(L);
2)线段树把区间上的任意长度为L的线段都分成不超过2log(L)线段。

败者树与胜者树

胜者树和败者树都是完全二叉树,是树形选择排序的一种变型。在胜者树、败者树中,每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛。不同的是,胜者树中的每个非终端结点均表示其左、右孩子结点中的“胜者”;而在败者树中,父结点中记下刚进行完的这场比赛中的败者,而让胜者去参加更高一层的比赛

哈夫曼树代码实现

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
 

public class HuffmanTree {

public static class Node&lt;E&gt; {
    E data;
    double weight;
    Node leftChild;
    Node rightChild;

public Node(E data, double weight) {
    super();
    this.data = data;
    this.weight = weight;
}

public String toString() {
    return "Node[data=" + data + ", weight=" + weight + "]";
}

}

public static void main(String[] args) {
List&lt;Node&gt; nodes = new ArrayList&lt;Node&gt;();

nodes.add(new Node("A", 40.0));
nodes.add(new Node("B", 8.0));
nodes.add(new Node("C", 10.0));
nodes.add(new Node("D", 30.0));
nodes.add(new Node("E", 10.0));
nodes.add(new Node("F", 2.0));

Node root = HuffmanTree.createTree(nodes);

System.out.println(breadthFirst(root));

}

/**

  • 构造哈夫曼树

  • @param nodes

  •        节点集合
    
  • @return 构造出来的哈夫曼树的根节点
    */
    private static Node createTree(List&lt;Node&gt; nodes) {
    // 只要nodes数组中还有2个以上的节点
    while (nodes.size() &gt; 1) {
    quickSort(nodes);
    //获取权值最小的两个节点
    Node left = nodes.get(nodes.size()-1);
    Node right = nodes.get(nodes.size()-2);

    //生成新节点,新节点的权值为两个子节点的权值之和
    Node parent = new Node(null, left.weight + right.weight);

    //让新节点作为两个权值最小节点的父节点
    parent.leftChild = left;
    parent.rightChild = right;

    //删除权值最小的两个节点
    nodes.remove(nodes.size()-1);
    nodes.remove(nodes.size()-1);

    //将新节点加入到集合中
    nodes.add(parent);
    }

    return nodes.get(0);

}

/**

  • 将指定集合中的i和j索引处的元素交换
  • @param nodes
  • @param i
  • @param j
    */
    private static void swap(List&lt;Node&gt; nodes, int i, int j) {
    Node tmp;
    tmp = nodes.get(i);
    nodes.set(i, nodes.get(j));
    nodes.set(j, tmp);
    }

/**

  • 实现快速排序算法,用于对节点进行排序
  • @param nodes
  • @param start
  • @param end
    */
    private static void subSort(List&lt;Node&gt; nodes, int start, int end) {
    if (start &lt; end) {
    // 以第一个元素作为分界值
    Node base = nodes.get(start);
    // i从左边搜索,搜索大于分界值的元素的索引
    int i = start;
    // j从右边开始搜索,搜索小于分界值的元素的索引
    int j = end + 1;
    while (true) {
    // 找到大于分界值的元素的索引,或者i已经到了end处
    while (i &lt; end &amp;&amp; nodes.get(++i).weight &gt;= base.weight)
    ;
    // 找到小于分界值的元素的索引,或者j已经到了start处
    while (j &gt; start &amp;&amp; nodes.get(–j).weight &lt;= base.weight)
    ;

    if (i &lt; j) {
    swap(nodes, i, j);
    } else {
    break;
    }
    }

    swap(nodes, start, j);

    //递归左边子序列
    subSort(nodes, start, j - 1);
    //递归右边子序列
    subSort(nodes, j + 1, end);
    }

}

public static void quickSort(List&lt;Node&gt; nodes){
subSort(nodes, 0, nodes.size()-1);
}

//广度优先遍历
public static List&lt;Node&gt; breadthFirst(Node root){
Queue&lt;Node&gt; queue = new ArrayDeque&lt;Node&gt;();
List&lt;Node&gt; list = new ArrayList&lt;Node&gt;();

if(root!=null){
    //将根元素加入“队列”
    queue.offer(root);
}

while(!queue.isEmpty()){
    //将该队列的“队尾”元素加入到list中
    list.add(queue.peek());
    Node p = queue.poll();
    
    //如果左子节点不为null,将它加入到队列
    if(p.leftChild != null){
        queue.offer(p.leftChild);
    }
    
    //如果右子节点不为null,将它加入到队列
    if(p.rightChild != null){
        queue.offer(p.rightChild);
    }
}

return list;

}

}

概念

图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和边集分别记为V(G)和E(G)。依据图的边集是否为有向,可把图分为有向图和无向图,根据图是否有权重,可以分为有权图和无权图

术语

  1. 邻接点-在一个无向图中,若存在一条边<Vi,Vj>,则称Vi,Vj为此边的两个端点,并称它们互为邻接点
  2. 出/入边-在一个有向图张,若存在一条边<Vi,Vj>,则称此边为顶点Vi的出边,顶点Vj的一条入边
  3. 度/入度/出度-无向图中的度定义为以该顶点为一个端点的边的数目,记为D(V)。有向图的入度定为多少边指向该顶点,出度是该顶点出边的个数

邻接矩阵

邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵是的row和col表示的是1....n个点。对于无向图 如果顶点b1和b2是连接的,那么在二维矩阵中matrix[b1,b2]和matrix[b2,b1]位置的值置为1,如果是有向图b1指向b2,那么 matrix[b1,b2]=1,matrix[b2,b1]=0;下面用一个例子表示无向图和有向图的邻接矩阵;

如果图是一个带权图,需要把1换为相应边上的权值,把非对角线上的换成一个很大的特定的实数则可,表示相应的边不存在,这个特定的实数通常用无穷大或MaxValue来表示,他要大于图G中所有边的权值

代码实现

邻接表

邻接矩阵与邻接表相比,它会造成空间的一定损失,它需要为每个顶点都分配n个边的空间,其实有很多边都是不存在边,但是邻接表的实现就不一样,它只关心存在的边,不关心不存在的边。邻接表由数组+链表组成对于上面的无向图,邻接表表示为(由于有向和无向的差别不是太大,所以只是画出了无向的邻接表表示)

代码实现

代码实现

邻接矩阵实现

package Graph;
//边集数组 ,存放边的信息
//邻域数组表示  和 邻域表表示  是两种不同的表示方式
//表示的是插入边的元素,边的起点和终点  边的权重
public class EdgeElement {
    int fromvex;
    int endvex;
    int weight;
    
public EdgeElement(int v1,int v2){
    //对于无权重图的初始化
    fromvex=v1;
    endvex=v2;
    weight=1;
}
public EdgeElement(int v1,int v2,int wgt){
    //对于有权重图的初始化
    fromvex=v1;
    endvex=v2;
    weight=wgt;
}	

}

package Graph;
//可以通过边集来得到一个图的构成
public interface Graph {
void creatGraph(EdgeElement d[]); //通过边结点来构建一个图
GraphType graphType(); //返回图的类型 无向无权图 无向有权图 有向无权图 有向有权图 定义一个枚举变量
int vertices(); //返回图的顶点数
int edges(); //返回图的边数
boolean find(int i,int j); //从图中查找一条边(i,j)是否存在
void putEdge(EdgeElement theEdge); //像图中插入一条边 theEdge
void removeEdge(int i,int j); //从图中删除一条边
int degree(int i); //返回顶点i的度
int inDegree(int i); //返回顶点i的入度
int outDegree(int i); //返回顶点i的出度
void output(); //以图的顶点集和边集的形式输出一个图
void depthFirstSearch(int v); //从顶点v开始深度优先搜索整幅图
void breadthFirstSearch(int v); //从顶点v开始广度优先搜索整幅图
}

//在邻域数组中写数据
public void creatGraph(EdgeElement[] d) {
int i;
for(i=0;i<d.length;i++){
if(d[i]==null) break;
int v1,v2;
v1=d[i].fromvex;
v2=d[i].endvex;
if(v1<0 || v1>n-1 || v2<0 || v2>n-1 || v1==v2){
System.out.println(“边的顶点序号无效,退出运行”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight){
a[v1][v2]=a[v2][v1]=1;
}else if(type==GraphType.NoDirectionWeight){
a[v1][v2]=a[v2][v1]=d[i].weight;
}else if(type==GraphType.DirectionNoWeight){
a[v1][v2]=1;
}else{
a[v1][v2]=d[i].weight;
}
}
e=i; //边的数目
}

public void putEdge(EdgeElement theEdge) {
int v1,v2;
v1=theEdge.fromvex;
v2=theEdge.endvex;
if(v1<0 || v1>n-1 || v2<0 || v2>n-1 || v1==v2){
System.out.println(“边的顶点序号无效,退出运行!”);
System.exit(0);
}
if(a[v1][v2]==0 || a[v1][v2]==MaxValue) e++; //边数e的值加一
if(type==GraphType.NoDirectionNoWeight || type==GraphType.NoDirectionWeight){
if(type==GraphType.NoDirectionNoWeight){
a[v1][v2]=a[v2][v1]=1;
}else{
a[v1][v2]=a[v2][v1]=theEdge.weight;
}
}else{
if(type==GraphType.DirectionNoWeight) a[v1][v2]=1;
else{
a[v1][v2]=theEdge.weight;
}
}
}

public void removeEdge(int i, int j) {
if(i<0 || i>n-1 || j<0 || j>n-1 || i==j){
System.out.println(“边的顶点序号无效,退出运行!”);
System.exit(0);
}
if(a[i][j]==0 || a[i][j]==MaxValue){
System.out.println(“要删除的边不存在,退出运行!”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight){
a[i][j]=a[j][i]=0;
}else if(type==GraphType.NoDirectionWeight){
a[i][j]=a[j][i]=MaxValue;
}else if(type==GraphType.DirectionNoWeight){
a[i][j]=0;
}else a[i][j]=MaxValue;
e–;
}

//得到该结点的度
public int degree(int i) {
if(i<0 || i> n-1){
System.out.println(“参数的顶点序号值无效,退出运行”);
System.exit(0);
}
int k=0;
if(type==GraphType.NoDirectionNoWeight || type==GraphType.NoDirectionWeight){
for(int j=0;j<n;j++){
if(a[i][j]!=0 && a[j][i]!=MaxValue) k++;

    }
}else{
    k = inDegree(i)+outDegree(i);
}
return k;

}
//入度
public int inDegree(int i) {
if(i<0 || i> n-1){
System.out.println(“参数的顶点序号值无效,退出运行”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight || type==GraphType.NoDirectionWeight){
return -1;
}
int k=0;
for(int j=0;j<n;i++){
if(a[j][i]!=0 && a[j][i]!=MaxValue) k++;
}
return k;
}
//出度
public int outDegree(int i) {
if(i<0 || i> n-1){
System.out.println(“参数的顶点序号值无效,退出运行”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight || type==GraphType.NoDirectionWeight){
return -1;
}
int k=0;
for(int j=0;j<n;i++){
if(a[i][j]!=0 && a[i][j]!=MaxValue) k++;
}
return k;
}

//输出
public void output() {
int i,j;
System.out.print(“V={“);//输出顶点集合
for(i=0;i<n-1;i++){
System.out.print(i+”,”);
}
System.out.print(n-1+”}”);//输出顶点集合
//输出边集合
System.out.print(“E={“);
if(type==GraphType.NoDirectionNoWeight || type==GraphType.DirectionNoWeight){
for(i=0;i<n;i++){
for(j=0;j<n;j++){
if(a[i][j]!=0 && a[i][j]!=MaxValue){
if(type==GraphType.NoDirectionNoWeight){
if(i<j)System.out.print(“(“+i+”,”+j+”),”);
}else{
System.out.print(“<”+i+”,”+j+”>”);
}
}
}
}
}else{
for(i=0;i<n;i++){
for(j=0;j<n;j++){
if(a[i][j]!=0 && a[i][j]!=MaxValue){
if(type==GraphType.NoDirectionWeight){
if(i<j)System.out.print(“(“+i+”,”+j+”)”+a[i][j]+”,”);
}else System.out.print(“<”+i+”,”+j+”>”+a[i][j]+”,”);
}
}
}
}
System.out.print(“}”);
}

//深度优先进行搜索 是从哪个顶点开始遍历,这里可以用顶点序号表示顶点
public void depthFirstSearch(int v) { //驱动函数
boolean visited[]=new boolean[n];
for(int i=0;i<n;i++){
visited[i]=false;
}
dfs(v,visited); //把每个结点遍历一次。
System.out.println();
}
//进行深度优先搜索的内部递归方法使用
private void dfs(int i,boolean visited[]){ //工作函数
System.out.print(i+” “);
visited[i]=true;
for(int j=0;j<n;j++){
if(a[i][j]!=0 && a[i][j]!=MaxValue && !visited[j]){
dfs(j,visited);
}
}
}

邻接表实现

package GraphLink;
//定义邻接表类型
public class EdgeNode{
    //需要一个存储自身结点
    int adjvex;
    int weight;
    EdgeNode next;
    //无权图
    public EdgeNode(int adj,EdgeNode nt){
        this.adjvex=adj;
        this.next=nt;
        this.weight=1;
    }
    //有权图
    public EdgeNode(int adj,int wgt,EdgeNode nt){
        this.adjvex=adj;
        this.weight=wgt;
        this.next=nt;
    }
}

//生成图函数
@Override
public void creatGraph(EdgeElement[] d) {
int i;
for(i=0;i<d.length;i++){//处理边集合 如果边集合重复 那程序不就有问题了么 这点要处理
if(d[i]==null) break;
int v1,v2,weight;
v1=d[i].fromvex;
v2=d[i].endvex;
weight=d[i].weight;
if(v1<0||v1>n-1||v2<0||v2>n-1||v1==v2){
System.out.println(“边的顶点序号无效,退出运行”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight){//处理无方向 无权重的图
a[v1]=new EdgeNode(v2,a[v1]);//把边挂载在主干上,a为EdgeNode类型的一维数组
a[v2]=new EdgeNode(v1,a[v2]);//处理第二条边
}else if(type==GraphType.NoDirectionWeight){//处理无向有权图
a[v1]=new EdgeNode(v2,weight,a[v1]);
a[v2]=new EdgeNode(v1,weight,a[v2]);
}else if(type==GraphType.DirectionNoWeight){//处理有向无权图
a[v1]=new EdgeNode(v2,a[v1]);
}else {
a[v1]=new EdgeNode(v2,weight,a[v1]);
}
}
e=i;
}

//在图中查找一条边
public boolean find(int v1,int v2){
if(v1<0||v1>n-1||v2<0||v2>n-1||v1==v2){
System.out.println(“边的顶点序号无效,退出运行”);
System.exit(0);
}
EdgeNode p=a[v1];
while(p!=null){
if(p.adjvex==v2){
return true;
}
p=p.next;
}
return false;
}

//向图中插入一条边
public void putEdge(EdgeElement theEdge){
int v1,v2,weight;
v1=theEdge.fromvex;
v2=theEdge.endvex;
weight=theEdge.weight;
if(v1<0||v1>n-1||v2<0||v2>n-1||v1==v2){
System.out.println(“边的顶点序号无效,退出运行”);
System.exit(0);
}
EdgeNode p=a[v1];
while(p!=null){
if(p.adjvex==v2){
break;//退出后处理
}
p=p.next;
}
if(p==null) e++;
else{
if(type==GraphType.DirectionWeight || type==GraphType.NoDirectionWeight){
p.weight=weight;
}
if(type==GraphType.NoDirectionWeight){//无向有权重的另一条边也要处理
EdgeNode q=a[v2];
while(q!=null){
if(q.adjvex==v1) break;
q=q.next;
}
q.weight=weight;
}
return;
}
if(type==GraphType.NoDirectionNoWeight){//如果是无向无权重
a[v1]=new EdgeNode(v2, a[v1]);
a[v2]=new EdgeNode(v1, a[v2]);
}else if(type==GraphType.NoDirectionWeight){//处理无向有权重
a[v1]=new EdgeNode(v2,weight,a[v1]);
a[v2]=new EdgeNode(v1,weight,a[v2]);
}else if(type==GraphType.DirectionNoWeight){//有向无权重
a[v1]=new EdgeNode(v2,a[v1]);
}else{
a[v1]=new EdgeNode(v2, weight,a[v1]);
}
}

public void removeEdge(int v1,int v2){
if(v1<0||v1>n-1||v2<0||v2>n-1||v1==v2){
System.out.println(“边的顶点序号无效,退出运行”);
System.exit(0);
}
EdgeNode p=a[v1],q=null;//拿到主干结点
while(p!=null){
if(p.adjvex==v2) break;
q=p;
p=p.next;
}
if(p==null){
System.out.println(“要删除的边不存在,程序退出运行”);
System.exit(0);
}
if(q==null){//该结点在表头上 主干的节点就是需要找的结点
a[v1]=a[v1].next;
}else{
q.next=p.next;//嫁接上
}
//删除无向图的另一个结点上的边
if(type==GraphType.NoDirectionNoWeight||type==GraphType.NoDirectionWeight){
EdgeNode p1=a[v2],q1=null;
while(p1!=null){
if(p1.adjvex==v1){
break;
}
q1=p1;
p1=p1.next;
}
if(q1==null){
a[v2]=a[v2].next;
}else{
q1.next=p1.next;
}
}
e–;
}

//返回一个顶点的度,度分为入度和出度,要分别处理
public int degree(int i){
if(i<0||i>n-1){
System.out.println(“顶点超过了范围,程序退出运行”);
System.exit(0);
}
int k=0;
if(type==GraphType.NoDirectionNoWeight||type==GraphType.NoDirectionWeight){
EdgeNode p=a[i];
while(p!=null){
k++;
p=p.next;
}
return k;
}else return inDegree(i)+outDegree(i);
}
//求出并返回一个顶点的入度
public int inDegree(int i){//返回指向该顶点的度,入度,用双循环来实现
int k=0;//记录入度个数
if(i<0||i>n-1){
System.out.println(“顶点超过了范围,程序退出运行”);
System.exit(0);
}
if(type==GraphType.NoDirectionNoWeight||type==GraphType.NoDirectionWeight){
return -1;
}else{
for(int j=0;j<n;j++){
EdgeNode p=a[j];
while(p!=null){
if(p.adjvex==i)k++;
p=p.next;
}
}
}
return k;
}
//返回一个顶点的出度
public int outDegree(int i){
int k=0;//记录出度的数目
EdgeNode p=a[i];
while(p!=null){
k++;
p=p.next;
}
return k;
}

//得到邻接矩阵
public int[][] getAdjacencyMatrix(){
int adjacencyMatrix[][]=new int[n][n];
if(type==GraphType.DirectionNoWeight||type==GraphType.DirectionWeight){//有向性
//有向 那不存在的边是存在一个InfinityValue
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i==j) adjacencyMatrix[i][j]=0;
else adjacencyMatrix[i][j]=InfinityValue;
}
}
}else{
//无向 都设置为0
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
adjacencyMatrix[i][j]=0;
}
}
}
//遍历整个图
for(int i=0;i<n;i++){
EdgeNode p=a[i];
while(p!=null){
adjacencyMatrix[i][p.adjvex]=p.weight;
p=p.next;
}
}
return adjacencyMatrix;
}

平衡树,子节点数目可以达到4个

特点

  1. 若父节点中存有1个数据项,则必有2个子节点。

  2. 若父节点中存有2个数据项,则必有3个子节点。

  3. 若父节点中存有3个数据项,则必有4个子节点。

构造

插入数据:

  • 未满节点直接插入

  • 满节点 ABC 分裂 A不变 B到父节点 C到右侧

代码实现

class Node
{
private static final int ORDER = 4;
private int numItems;//节点中实际存储的数据项数目,其值一定不大于3
private Node parent;
private Node childArray[] = new Node[ORDER];//子节点数组
private DataItem itemArray[] = new DataItem[ORDER-1];//存储数据项数组
//-------------------------------------------------------------
// 把参数中的节点作为子节点,与当前节点进行连接
public void connectChild(int childNum, Node child)
   {
   childArray[childNum] = child;
   if(child != null)
      child.parent = this;//当前节点作为父节点
   }
//-------------------------------------------------------------
// 断开参数确定的节点与当前节点的连接,这个节点一定是当前节点的子节点。
public Node disconnectChild(int childNum)
   {
   Node tempNode = childArray[childNum];
   childArray[childNum] = null; //断开连接
   return tempNode;//返回要这个子节点
   }
//-------------------------------------------------------------
public Node getChild(int childNum)//获取相应的子节点
   { return childArray[childNum]; }
//-------------------------------------------------------------
public Node getParent()//获取父节点
   { return parent; }
//-------------------------------------------------------------
public boolean isLeaf()//是否是叶结点
   { return (childArray[0]==null) ? true : false; }//叶结点没有子节点
//-------------------------------------------------------------
public int getNumItems()//获取实际存储的数据项数目
  { return numItems; }
//-------------------------------------------------------------
public DataItem getItem(int index)   // 获取具体的数据项
   { return itemArray[index]; }
//-------------------------------------------------------------
public boolean isFull()//该节点是否已满
   { return (numItems==ORDER-1) ? true : false; }
//-------------------------------------------------------------
public int findItem(long key)       // 查找
   {                                    
   for(int j=0; j<ORDER-1; j++)         // 遍历数组
      {                                 
      if(itemArray[j] == null)          // 数组未满,未找到
         break;
      else if(itemArray[j].dData == key)
         return j;
      }
   return -1;
   }  // end findItem
//-------------------------------------------------------------
public int insertItem(DataItem newItem)//节点未满的插入
   {
   numItems++;                          
   long newKey = newItem.dData;         // 获得关键字
 

for(int j=ORDER-2; j>=0; j–) // 因为节点未满,所以从倒数第二项向前查找
{
if(itemArray[j] == null) // 没存数据
continue;
else
{
long itsKey = itemArray[j].dData;//获得关键字
if(newKey < itsKey) //插入位置在其前面,但未必相邻
itemArray[j+1] = itemArray[j]; //当前数据项后移
else
{
itemArray[j+1] = newItem; // 在其后位置插入
return j+1; // 返回插入的位置下标
} // new item
} // end else (not null)
} // end for // shifted all items,
//若上述代码没有执行返回操作,那么这是空节点(只有初始时根是这个情况)
itemArray[0] = newItem; // insert new item
return 0;
} // end insertItem()
//————————————————————-
public DataItem removeItem() // 移除数据项,从后向前移除
{
// 假设节点非空
DataItem temp = itemArray[numItems-1]; // 要移除的数据项
itemArray[numItems-1] = null; // 移除
numItems–; // 数据项数目减一
return temp; // 返回要移除的数据项
}
//————————————————————-
public void displayNode() // format “/24/56/74/“
{
for(int j=0; j<numItems; j++)
itemArray[j].displayItem(); // “/56”
System.out.println(“/“); // final “/“
}
//————————————————————-
} // end class Node
////////////////////////////////////////////////////////////////

class Tree234
{
private Node root = new Node(); // 创建树的根
//————————————————————-
//获取查找的下一个节点
public Node getNextChild(Node theNode, long theValue)
{
int j;
// 假设这个节点不是叶结点
int numItems = theNode.getNumItems();//获得当前节点的数据项数目
for(j=0; j<numItems; j++)
{
if( theValue < theNode.getItem(j).dData )
return theNode.getChild(j); // 返回相应的节点
} // end for
return theNode.getChild(j); // 此时j=numItems
}
//————————————————————-
public int find(long key)
{
Node curNode = root;
int childNumber;
while(true)
{
if(( childNumber=curNode.findItem(key) ) != -1)//每次循环这句一定执行
return childNumber; // found it
else if( curNode.isLeaf() )//叶结点上也没找到
return -1; // can’t find it
else // 不是叶结点,则继续向下查找
curNode = getNextChild(curNode, key);
} // end while
}
//————————————————————-
// 插入数据项
public void insert(long dValue)
{
Node curNode = root;//当前节点标志
DataItem tempItem = new DataItem(dValue);//插入数据项封装

while(true)
{
if( curNode.isFull() ) // 是满节点
{
split(curNode); // 分裂
curNode = curNode.getParent(); // 回到分裂出的父节点上
// 继续向下查找
curNode = getNextChild(curNode, dValue);
} // end if(node is full)
//后面的操作中节点都未满,否则先执行上面的代码
else if( curNode.isLeaf() ) // 是叶结点,非满
break; // 跳出,直接插入

  else
     curNode = getNextChild(curNode, dValue);//向下查找
  }  // end while

curNode.insertItem(tempItem); // 此时节点一定不满,直接插入数据项,
} // end insert()
//————————————————————-
public void split(Node thisNode) // 分裂
{
// 操作中节点一定是满节点,否则不会执行该操作
DataItem itemB, itemC;
Node parent, child2, child3;
int itemIndex;

itemC = thisNode.removeItem(); // 移除最右边的两个数据项,并保存为B和C
itemB = thisNode.removeItem(); //
child2 = thisNode.disconnectChild(2); // //断开最右边两个子节点的链接
child3 = thisNode.disconnectChild(3); //

Node newRight = new Node(); //新建一个节点,作为当前节点的兄弟节点

if(thisNode==root) // 是根
{
root = new Node(); // 新建一个根
parent = root; // 把新根设为父节点
root.connectChild(0, thisNode); // 连接父节点和子节点
}
else // 不是根
parent = thisNode.getParent(); // 获取父节点

itemIndex = parent.insertItem(itemB); // 把B插入父节点中,返回插入位置
int n = parent.getNumItems(); // 获得总数据项数目

for(int j=n-1; j>itemIndex; j–) //从后向前移除
{
Node temp = parent.disconnectChild(j); // 断开连接
parent.connectChild(j+1, temp); // 连接到新的位置
}

parent.connectChild(itemIndex+1, newRight);//连接到新位置

// 处理兄弟节点
newRight.insertItem(itemC); // 将C放入兄弟节点中
newRight.connectChild(0, child2); // 把子节点中最右边的两个连接到兄弟节点上
newRight.connectChild(1, child3); //
} // end split()
//————————————————————-
// gets appropriate child of node during search for value

public void displayTree()
{
recDisplayTree(root, 0, 0);
}
//————————————————————-
private void recDisplayTree(Node thisNode, int level,
int childNumber)
{
System.out.print(“level=”+level+” child=”+childNumber+” “);
thisNode.displayNode(); // display this node

// call ourselves for each child of this node
int numItems = thisNode.getNumItems();
for(int j=0; j<numItems+1; j++)
{
Node nextNode = thisNode.getChild(j);
if(nextNode != null)
recDisplayTree(nextNode, level+1, j);
else
return;
}
} // end recDisplayTree()
//————————————————————-
} // end class Tree234
////////////////////////////////////////////////////////////////

import java.io.*;

class Tree234App
{
public static void main(String[] args) throws IOException
{
long value;
Tree234 theTree = new Tree234();

theTree.insert(50);
theTree.insert(40);
theTree.insert(60);
theTree.insert(30);
theTree.insert(70);

while(true)
{
System.out.print(“Enter first letter of “);
System.out.print(“show, insert, or find: “);
char choice = getChar();
switch(choice)
{
case ‘s’:
theTree.displayTree();
break;
case ‘i’:
System.out.print(“Enter value to insert: “);
value = getInt();
theTree.insert(value);
break;
case ‘f’:
System.out.print(“Enter value to find: “);
value = getInt();
int found = theTree.find(value);
if(found != -1)
System.out.println(“Found “+value);
else
System.out.println(“Could not find “+value);
break;
default:
System.out.print(“Invalid entry\n”);
} // end switch
} // end while
} // end main()
//————————————————————–
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//————————————————————–
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}

//————————————————————-
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//————————————————————-
} // end class Tree234App
////////////////////////////////////////////////////////////////

选择排序(Selection Sort)是一种简单直观的排序算法,它在每一轮中选择未排序部分中的最小(或最大)元素,并将其放置在已排序部分的末尾。以下是一个选择排序的Java代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.Arrays;

public class SelectionSort {

public static void selectionSort(int[] arr) {
int n = arr.length;

for (int i = 0; i < n - 1; i++) {
int minIndex = i;

// 找到未排序部分的最小值索引
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}

// 将最小值与未排序部分的第一个元素交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}

public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11};
System.out.println("Original array: " + Arrays.toString(arr));

selectionSort(arr);

System.out.println("Sorted array: " + Arrays.toString(arr));
}
}

在这个示例中,selectionSort 方法使用选择排序算法对整数数组进行排序。它从未排序部分中选择最小值,并将其与未排序部分的第一个元素交换。通过多次迭代,每次选择一个最小值,最终数组会变得有序。

尽管选择排序的时间复杂度较高(O(n^2)),但它在简单场景中具有一定的实用性。

计数排序(Counting Sort)是一种非比较性的排序算法,适用于待排序数组中元素的范围比较小的情况。以下是一个计数排序的Java代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.Arrays;

public class CountingSort {

public static void countingSort(int[] arr) {
int n = arr.length;

// 找到数组中的最大值和最小值
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();

// 创建计数数组,大小为最大值和最小值之差加1
int range = max - min + 1;
int[] countArray = new int[range];

// 统计每个元素出现的次数
for (int i = 0; i < n; i++) {
countArray[arr[i] - min]++;
}

// 根据计数数组重新构造排序后的数组
int index = 0;
for (int i = 0; i < range; i++) {
while (countArray[i] > 0) {
arr[index++] = i + min;
countArray[i]--;
}
}
}

public static void main(String[] args) {
int[] arr = {4, 2, 2, 8, 3, 3, 1};
System.out.println("Original array: " + Arrays.toString(arr));

countingSort(arr);

System.out.println("Sorted array: " + Arrays.toString(arr));
}
}

在这个示例中,countingSort 方法使用计数排序算法对整数数组进行排序。它首先找到数组中的最大值和最小值,然后创建一个计数数组来统计每个元素出现的次数。最后,根据计数数组重新构造排序后的数组。

请注意,计数排序适用于元素范围较小的情况,因为它的性能与元素范围有关。

桶排序(Bucket Sort)是一种排序算法,它将输入数据分割成若干个桶(区间范围),然后对每个桶中的数据进行排序,最后将各个桶中的数据合并起来。以下是一个桶排序的Java代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class BucketSort {

public static void bucketSort(double[] arr) {
int n = arr.length;
List<Double>[] buckets = new ArrayList[n];

// 初始化桶
for (int i = 0; i < n; i++) {
buckets[i] = new ArrayList<>();
}

// 将元素放入对应的桶中
for (int i = 0; i < n; i++) {
int bucketIndex = (int) (n * arr[i]);
buckets[bucketIndex].add(arr[i]);
}

// 对每个桶中的元素进行排序
for (int i = 0; i < n; i++) {
Collections.sort(buckets[i]);
}

// 将排序后的桶合并成一个有序数组
int index = 0;
for (int i = 0; i < n; i++) {
for (double value : buckets[i]) {
arr[index++] = value;
}
}
}

public static void main(String[] args) {
double[] arr = {0.42, 0.32, 0.33, 0.52, 0.37, 0.47, 0.51};
System.out.println("Original array: " + Arrays.toString(arr));

bucketSort(arr);

System.out.println("Sorted array: " + Arrays.toString(arr));
}
}

在这个示例中,bucketSort 方法使用桶排序算法对一个小数数组进行排序。它首先将元素按照一定的规则放入不同的桶中,然后对每个桶中的元素进行排序,最后将排序后的桶合并成一个有序数组。

注意,这个示例中的桶排序适用于小数排序,每个桶内部使用了简单的插入排序。实际应用中,桶的数量和分桶规则的选择会影响算法的性能。

拓扑排序是用于有向无环图(DAG)的一种排序方式,它可以用来表示依赖关系,任务调度等情况。下面是一个拓扑排序的Java代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;

public class TopologicalSort {

public static List<Integer> topologicalSort(int vertices, List<Integer>[] graph) {
List<Integer> result = new ArrayList<>();
int[] inDegree = new int[vertices];

// 计算每个顶点的入度
for (List<Integer> neighbors : graph) {
for (int neighbor : neighbors) {
inDegree[neighbor]++;
}
}

// 将入度为0的顶点加入队列
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < vertices; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}

// 逐步移除入度为0的顶点,并更新相关顶点的入度
while (!queue.isEmpty()) {
int vertex = queue.poll();
result.add(vertex);

for (int neighbor : graph[vertex]) {
inDegree[neighbor]--;
if (inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}

return result;
}

public static void main(String[] args) {
int vertices = 6;
List<Integer>[] graph = new ArrayList[vertices];
for (int i = 0; i < vertices; i++) {
graph[i] = new ArrayList<>();
}

graph[5].add(2);
graph[5].add(0);
graph[4].add(0);
graph[4].add(1);
graph[2].add(3);
graph[3].add(1);

List<Integer> result = topologicalSort(vertices, graph);
System.out.println("Topological Sort: " + result);
}
}

在这个示例中,topologicalSort 方法使用拓扑排序算法对给定的有向无环图(使用邻接表表示)进行排序。该算法的核心思想是从入度为0的顶点开始,逐步移除顶点并更新相关顶点的入度。

以下是快速排序的Java代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.Arrays;

public class QuickSort {

public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}

private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;

for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;

return i + 1;
}

public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
System.out.println("Original array: " + Arrays.toString(arr));

int n = arr.length;
quickSort(arr, 0, n - 1);

System.out.println("Sorted array: " + Arrays.toString(arr));
}
}

在这个示例中,quickSort 方法使用快速排序算法对整数数组进行排序。快速排序是一种分治算法,它选取一个基准元素,将数组分成两个部分,一个部分包含小于基准的元素,另一个部分包含大于基准的元素,然后递归地对两个部分进行排序。

0%