非线性数据结构
非线性数据结构主要在数据结构里面包括树和图(有向图,无向图)
概念:度,树里面度就是子节点的个数,但是图分为入度和出度,入度就是能够通往该节点的线路的个数,出度就是该节点通往其他节点的路径的条数。
树
对于一般的树的存储方式:
- 孩子兄弟存储法 ,通过一棵树的同级的兄弟用右子树来表示,通过左子树来表示孩子,从而构成一棵二叉树;
- 孩子双亲表示法通过找到双亲的孩子,以及孩子的双亲来找到二叉树;
树的遍历
对于树的遍历,这里一般又三种遍历方法:
1.层次遍历(相当于图里面的DBS)通过先遍历第一层,再遍历第二层,最后遍历第三层的方法来实现层次遍历。
2.先序遍历(相当于图里面的DFS)通过先遍历根节点,在遍历根节点的左子树,一直往右进行遍历来实现先序遍历,同样的相当于在二叉树里面的先序遍历。
3.后序遍历:对于一般树的后序遍历,一般来说相当于二叉树的中序遍历,先将其他的子树遍历完成之后,再来遍历根节点
二叉树
首先二叉树的各种操作可以参考以下代码:
import java.util.Scanner;
import java.util.Stack;
import java.io.*;
class Data
{
int times=0;
char b;
}
class bnode{
bnode left,right;
//创建两个指针变量
Data data=new Data();
}
//注意:该实验实现二叉树的建立,二叉树的中序遍历先序遍历后序遍历以及一些其他的操作(注意遍历要递归和非递归)
public class BiTree {
static int number=0;
public static bnode creattree() throws IOException {
//创建一颗二叉树,采取先序遍历的方式
//以下是三种输入一个字符的方式:
//方法一
// System.out.println("请输:");
// Scanner scanner=new Scanner(System.in);
// String s=scanner.next();
// char m=s.charAt(0);
//方法二
// BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
// int a=br.read();
// 方法三
char m= (char) System.in.read();
// System.out.println(m);
if(m!='#'){
bnode root=new bnode();
// if(root.data!=null)
root.data.b=m;//把数据存入
number++;
root.left=creattree();
root.right=creattree();
return root;
}
else return null;
}
//遍历二叉树操作(递归操作)
public void recursion1(bnode root)
{
if(root==null)return ;
//完成递归操作(先序)
/*操作写在这里
*
* */
recursion1(root.left);
recursion1(root.right);
}
public void recurtion2(bnode root)
{
//这里写中序遍历
if(root==null)return;
recurtion2(root.left);
/*
* 这里写操作*/
recurtion2(root.right);
}
public void recurtion3(bnode root)
{
//这里写后序遍历
if(root==null)return ;
recurtion3(root.left);
recurtion3(root.right);
/*
* 这里写后序遍历的操作*/
}
//遍历二叉树操作(非递归)
public void no_recurtion1(bnode root)
{
//实现先序遍历
bnode p=root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if(root==null)return;
a.push(p);
while (!a.empty())
{
//栈不为空就可以进行操作了
p=a.pop();
/*这里写操作*/
if(p.right!=null)a.push(p.right);
if(p.left!=null)a.push(p.left);//后入栈先出来
}
}
public void no_recurtion2(bnode root)
{if(root==null)return;
//实现中序遍历
bnode p=root;
Stack<bnode> a=new Stack<>();
a.push(p);
while(!a.empty())
{
p=a.pop();
p.data.times++;//访问次数添加一次
if(p.right!=null) a.push(p.right);
if(p.data.times==1) a.push(p);
else if(p.data.times==2){/*
* 这里写操作*/
p.data.times=0;continue;}
if(p.left!=null) a.push(p.left);
}
}
public void no_recurtion3(bnode root)
{if(root==null)return;
//实现后序遍历
bnode p=root ;
Stack<bnode>a =new Stack<>();
a.push(p);
while(!a.empty())
{
//栈不为空
p=a.pop();
p.data.times++;
if(p.data.times==2)continue;
if(p.data.times==3) {
/*
* 这里写相应的操作*/
p.data.times=0;}
else {
a.push(p);
if (p.right != null) a.push(p.right);
a.push(p);
if (p.left != null) a.push(p.left);
}
}
}
//采用非递归实现求节点个数,数值最大值,最小值,以及度为一二的点,以及叶子节点
public int two(bnode root) {
//实现先序遍历,叶子结点
int num = 0;
bnode p = root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if (root == null) return 0;
a.push(p);
while (!a.empty()) {
//栈不为空就可以进行操作了
p = a.pop();
/*这里写操作 */
if (p.right != null && p.left != null) num++;
if (p.right != null) a.push(p.right);
if (p.left != null) a.push(p.left);//后入栈先出来
}
return num;
}
public int zero(bnode root) {
//实现先序遍历,度为二的点
int num = 0;
bnode p = root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if (root == null) return 0;
a.push(p);
while (!a.empty()) {
//栈不为空就可以进行操作了
p = a.pop();
/*这里写操作 */
if (p.right == null && p.left == null) num++;
if (p.right != null) a.push(p.right);
if (p.left != null) a.push(p.left);//后入栈先出来
}
return num;
}
public int one(bnode root) {
//实现先序遍历,度为一的点
int num = 0;
bnode p = root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if (root == null) return 0;
a.push(p);
while (!a.empty()) {
//栈不为空就可以进行操作了
p = a.pop();
/*这里写操作 */
if ((p.right != null || p.left != null)&&!(p.right != null && p.left != null)) num++;
if (p.right != null) a.push(p.right);
if (p.left != null) a.push(p.left);//后入栈先出来
}
return num;
}
public char max(bnode root) {
//实现先序遍历
bnode p = root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if (root == null) return 0;
a.push(p);
char max = root.data.b;
while (!a.empty()) {
//栈不为空就可以进行操作了
p = a.pop();
/*这里写操作 */
max=(char) Math.max(max,p.data.b);
if (p.right != null) a.push(p.right);
if (p.left != null) a.push(p.left);//后入栈先出来
}
return max;
}
public char min(bnode root) {
//实现先序遍历
bnode p = root;
Stack<bnode> a = new Stack<>();//建立一个栈结构为了存节点
if (root == null) return 0;
a.push(p);
char min = root.data.b;
while (!a.empty()) {
//栈不为空就可以进行操作了
p = a.pop();
/*这里写操作 */
min=(char) Math.min(min,p.data.b);
if (p.right != null) a.push(p.right);
if (p.left != null) a.push(p.left);//后入栈先出来
}
return min;
}
public int Getnum(bnode root)
{
//求节点个数
// if(root==null)return 0;
// //完成递归操作(先序)
//
// /*操作写在这里
// *
// * */
// number++;
// Getnum(this.root.left);
// Getnum(this.root.right);
// return number;
return number;//注意如果
}
// public int hashCode() {
//
// }
public int Get_depth(bnode root)
{
//求这棵树深度
//这里写中序遍历
if(root==null)return 0;
int num1=Get_depth(root.left);
int num2=Get_depth(root.right);
return num1>num2?num1+1:num2+1;//看这两个树的深度,返回大的那个
}
public int Get_sub_depth(char a,bnode root) {
//求子树深度(先序遍历)
if (root == null) return -1;
//实现后序遍历
bnode p = root;
Stack<bnode> b = new Stack<>();
b.push(p);
while (!b.empty()) {
//栈不为空
p = b.pop();
p.data.times++;
if (p.data.times == 2) continue;
if (p.data.times == 3) {
if (a == p.data.b) return Get_depth(p);
/*
* 这里写相应的操作*/
p.data.times = 0;
} else {
b.push(p);
if (p.right != null) b.push(p.right);
b.push(p);
if (p.left != null) b.push(p.left);
}
}
return -1;//没有这个子树的情况
}
public static void main (String[]args) throws IOException {
BiTree biTree=new BiTree();//主函数要是想调用类里面的非静态方法就要创建一个对象,这样才能只用该对象的非静态方法
System.out.println("请输入你的二叉树:");
bnode root=biTree.creattree();
int height=biTree.Get_depth(root);
System.out.println("这棵二叉树的深度为:"+height);
System.out.println("这棵二叉树的节点个数为:"+biTree.Getnum(root));
System.out.println("这棵树的度为零一二的点分别为:"+biTree.zero(root)+","+biTree.one(root)+","+biTree.two(root));
System.out.println("这棵树的数据最大值和数据最小值为:"+biTree.max(root)+","+biTree.min(root));
}
}
对于二叉树的操作有很多比如创建一棵二叉树,创建的时候就要用先序遍历创建二叉树,因为要现有一个根节点。遍历一棵二叉树,对于二叉树的遍历操作,有:
先序遍历
先序遍历是先找到根节点,首先遍历根节点,然后找到左子树,对左子树进行遍历,同样也是先遍历左子树的根节点,然后遍历完做指数之后,再对右子树进行同样的操作,先遍历右子树的根节点,然后再遍历右子树的左子树,以此类推。
中序遍历
略
后序遍历
略
线索二叉树
线索二叉树即通过不需要的指针域存储上一个节点的有关信息,从而能更好的遍历二叉树,这样的二叉树的实现可以用一个ltag以及rtag实现判断这两个指针到底是指向孩子节点还是父亲节点。
树变为二叉树
树变为二叉树之后,二叉树的左指针域为空的节点,对应原来的树里面两个指针域都为空的节点。(右指针域存储的都是兄弟节点,左指针域存的都是孩子节点)
二叉树里面的一些结论
满二叉树的节点个数:2k-1
满二叉树的第k层的节点个数:2k-1
完全二叉树的有多少层:[logn]-1
二叉树度为二的点的个数于度为零的点的个数的关系:n0=n2+1
二叉树孩子节点的个数:n-1
哈夫曼树
哈夫曼树也成为最优二叉树,形成过程是:
先找到两个权值最小的点,两个点合成一个节点。
再从剩下的点里面找到两个最小的点,在合成一个点。
以此类推,直到最后一个点,形成一棵最后二叉树即可。
关于Huffman编码,可以定义向右走就是一向左走就是零,如果需要找到一个叶子节点,可以找到他的路径,然后得到一串固定的Huffman编码。
如下是Huffman树的构建以及操作:
import java.util.Scanner;
public class Huffman {
//Huffman编码
//分配五十一个节点的空间,用来存储数据
static node []node=new node[53];
public void initnode()
{
for(int i=0;i<53;i++)
{
node[i]=new node();
}
}
Huffman()
{
initnode();
init();
}
public void init()
{
putw(node);//付给权值
for(int i=0;i<26;i++)
{
//生成二十六次新节点
Getmin(node);
}
}
public void putw(node[]a)
{
int []w={186,64,13,22,32,103,21,15,47,57,4,5,32,20,57,63,15,3,48,51,80,23,8,18,2,16,1};
char []data={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
//给他们权值
for(int i=0;i<w.length;i++)
{
a[i].data=data[i];
a[i].w=w[i];
}
}
public void Getmin(node[]a)
{
//将两个最小的合成一个节点
int min = 0;//最小的权值
int t=0,inmin=0;//所在位置
int flag=0;
for(int i=0;i<a.length;i++)
{
if(a[i].w==0){t=i;break;}//节点为空
else{
if(a[i].parent == -1){
//先赋值,注意如果没有比第一个小的(第一个的父亲节点已经赋值,那就不能在用第一个节点开始了)
if(flag==0){min=a[i].w;flag++;inmin=i;}
//第一个节点没有赋父亲节点的值
if(a[i].w<min){
min=a[i].w;
inmin=i;
}
}
}
}
a[inmin].parent=t;
a[t].left=inmin;
min=a[0].w;
inmin=0;//重新付给初始值
for(int j=0;j<t;j++)
{
if(a[j].parent == -1){
if(flag==1){min=a[j].w;flag++;inmin=j;}
if(a[j].w<min){
min=a[j].w;
inmin=j;
}
}
//糖
}
a[inmin].parent=t;
a[t].ringht=inmin;
a[t].w=a[a[t].getRinght()].w+a[a[t].getLeft()].w;//给父亲节点权值
//给第t个节点赋值
}
public String Huff(char i)
{
int num=0;
String A=" ";
for (int j = 0; j <27 ; j++) {
//遍历有字母的节点
if(i==node[j].data){A=Huff(j);break;}
}
StringBuilder a=new StringBuilder(A);
a.reverse();
return a.toString();
}
public String Huff(int i)
{
String a = " ";
//第i个字符的huffman编码
int p;
while (node[i].getParent()!=-1)
{
p=node[i].parent;//在循环里面要不断更新父亲节点的值
if(node[p].left==i)a+='0';
else a+='1';
i=node[i].getParent();
}
return a;
}
public String Hufftoword(String huff)
{
//put in huffman change it into a words
String word=new String();
int number;
String HUF[]=huff.split(" ");
for(int j=0;j<HUF.length;j++)
{number =52;
for(int i=0;i<HUF[j].length();i++)
{
//以左右子树为空为结束标志
if(HUF[j].charAt(i)=='0'){
//左走,向左子树
number= this.node[number].getLeft();
}
else if(HUF[j].charAt(i)=='1') {
number =this.node[number].getRinght();
}
if(this.node[number].getLeft()==-1 ){
word+=String.valueOf(this.node [number].data);
break;
}}
}
return word;
}
public String WordtoHuff(String word)
{
String huff=new String();
char []data={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
for(int i = 0; i<word.length(); i++)
{
for(int j=0;j<=this.word.length;j++)
{
if(data[j]==word.charAt(i)){huff+=this.word[j];break;}
}
}
return huff;
}
String word[]=new String[27];//创建二十七个字符对象的huffman编码
public static void main(String[] args) {
Huffman huffman=new Huffman();//创建一个Huffman对象
System.out.println("零代表左走,一代表右走!");
char []data={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
// for(int i=0;i<53;i++)
//{
// System.out.println(i+" "+huffman.node[i].data+" " +huffman.node[i].w+" parent:"+huffman.node[i].getParent()+" left:"+huffman.node[i].getLeft()+" right:"+huffman.node[i].getRinght());
//}
for(int i=0;i<27;i++)
{
huffman.word[i]=huffman.Huff(data[i]);//存入关于哈夫曼的数据
System.out.println(data[i]+":"+huffman.Huff(data[i]));
}
System.out.println(" 请输入您的一串字符转为字符串:");
Scanner scanner = new Scanner(System.in);
String h=scanner.nextLine();
System.out.println("转为huffman编码之后得到:");
String huf=huffman.WordtoHuff(h);
System.out.println(huf);
System.out.println("再次转为字符串得到:");
System.out.println(huffman.Hufftoword(huf));
}
}
class node{
int left=-1,ringht=-1,parent=-1;
char data=' ';
int w;//存放权值
public int getLeft() {
return left;
}
public int getParent() {
return parent;
}
public int getRinght() {
return ringht;
}
}
图
概念:图里面包括很多概念
邻接点:两个点被一条线相连,称这两个点是一对邻接点。
强连通图,强连通分量这两个概念是基于有向图的。
邻接图和邻接矩阵
邻接图:
邻接图是一种用静态链表表示n个节点的方式,用邻接图可以实现对于一个图的存储,对于每个图,邻接图不唯一,但是只要确定邻接图之后,他的DFS以及DBS都是固定的了。
邻接矩阵:
邻接矩阵是一种表示与某个节点是否有邻接关系的矩阵,可以用邻接矩阵的值表示权值,也可以用邻接矩阵的值表示是否有连接(比如,如果有连接可以用1来表示)。
对于不同的图有不同的表示方式,比如对于有向图,可以用列矩阵来表示起点,行矩阵来表示终点,对于无向图,邻接矩阵就是对称矩阵。
无向图节点的度为:与该节点相关联边的数目
有向图节点的出度为:由该节点发出的边的数目
有向图的节点的入度为:以该节点为终点的边的数目
生成树
生成树有n个节点,
有n-1条边,
在生成树中加一条边,必成为回路,
注意生成树不唯(广度优先生成树,深度优先生成树)
注意,生成树是基于原来的图,如果是非连通图,那就是生成森林。
最小生成树
如何得到带权的图里的最小生成树呢?
利用两个集合的最小生成树一定包含树两个集合连通的最小边
1.首先找出最小边,然后将最小边看为一个集合,继续找下一个最小边
2.以此类推,直到最后将所有的节点都找完。
关键路径
关键路径即路径长度最长的路径,对于关键路径还有最早发生时间和最晚发生时间的概念。
这里的最早发生时间当然好理解,最早发生时间就是到达这个路径最早开始的时间,最晚发生时间就是这个路径开始的最晚时间(最晚时间是相对于关键路径而言的)。