1.树
树是由n个数据构成的非线性结构,它是根朝上,叶朝下。
注意:树形结构之中,子树之间不能连接,不然就不构成树形结构
1.子树之间没有交集
2.除了根节点以外,每一个节点有且只有一个父亲节点
3.一个n个节点的树,有n-1条边
1.1树的一些概念
结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为6
树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为6
叶子结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等节点为叶结点 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
根结点:一棵树中,没有双亲结点的结点;如上图:A
结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推
树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 树的以下概念只需了解,
非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等节点为分支结点
兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点
结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
森林:由m(m>=0)棵互不相交的树组成的集合称为森林
2.二叉树
2.1概念
一棵二叉树的节点是有限集合
对于任意的二叉树,都是由下列情况组合而成
2.2两种特殊的二叉树
1.满二叉树:一棵二叉树树,每一层的节点数都到达了最大值,那么这就是满二叉树。也就是说如果一棵二叉树是k层,那么这棵树的节点树,且结点总数是-1,则它就是满二叉树。
2.完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.3二叉树的性质
1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有(i>0)个结点
2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是-1(k>=0)
3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
4. 具有n个结点的完全二叉树的深度k为上取整
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
若2i+1<n 左孩子序号:2i+1,否则没有左孩子
若2i+2<n 右孩子序号:2i+1,否则没有右孩子
2.4二叉树的存储
二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下
//孩子表示法
class Node {
int val; //用于储存数据
Node left; //表示左孩子,通常为以左子树为根节点的二叉树
Node right; //表示右孩子,通常为右子树为根节点的二叉树
}
// 孩子双亲表示法
class Node {
int val; // 数据域
Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
Node parent; // 当前节点的根节点
}
2.2二叉树的基本操作
存储方式是孩子表示方法
BinaryTree类
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class BinaryTree {
public static class TreeNode {
public int val;
public TreeNode left;//储存左子树
public TreeNode rigth;//储存右子树
public TreeNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "TreeNode{" +
"val=" + val +
'}';
}
}
//创建一个二叉树
public TreeNode createbinarytree() {
TreeNode A = new TreeNode(1);
TreeNode B = new TreeNode(2);
TreeNode C = new TreeNode(3);
TreeNode D = new TreeNode(4);
TreeNode E = new TreeNode(5);
TreeNode F = new TreeNode(6);
TreeNode G = new TreeNode(7);
TreeNode H = new TreeNode(8);
//连接数的结构
A.left = B;
A.rigth = C;
B.left = D;
B.rigth = E;
C.left = F;
C.rigth = G;
E.rigth = H;
return A;
}
//先实现三种遍历方法
// 前序遍历
public void preOrder(TreeNode root) {
//NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。
//递归的结束条件
if (root == null) {
return;
}
System.out.print(" " + root.val);
preOrder(root.left);
preOrder(root.rigth);
//走到结尾这个循环结束
}
//不使用递归的的方法实现前序遍历
public void preOrder1(TreeNode root){
//借用栈来实现
if(root==null){
return ;
}
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur);
System.out.print(cur.val+" ");
cur=cur.left;
}
TreeNode top=stack.pop();
cur=top.rigth;
}
}
// 中序遍历
public void inOrder(TreeNode root) {
//LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(" " + root.val);
inOrder(root.rigth);
}
//不使用递归的的方法实现中序遍历
public void inOrder1(TreeNode root){
//借用栈来实现i
if(root==null){
return ;
}
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
TreeNode top=stack.pop();
System.out.print(top.val+" ");
cur=top.rigth;
}
}
// 后序遍历
public void postOrder(TreeNode root) {
//LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。
if (root == null) {
return;
}
postOrder(root.left);
postOrder(root.rigth);
System.out.print(" " + root.val);
}
//使用不递归的方法使用后序遍历
public void postOrder1(TreeNode root){
//借用栈来实现
if(root==null){
return ;
}
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
TreeNode prve=null;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
TreeNode top=stack.peek();
if(top==null||top.rigth==prve){
System.out.println(top.val+" ");
prve= stack.pop();
}else{
cur=top.rigth;
}
}
}
//获取数中节点个数
public int nodesize;
//第一种遍历式获取
public void Nodesize1(TreeNode root) {
//采用前序遍历
if (root == null) {
return;
}
//遇见节点将打印改成nodesize++
nodesize++;
Nodesize1(root.left);
Nodesize1(root.rigth);
}
//第二种获取节点个数方法
//左子树节点个数+右子树节点个数+根节点个数
public int Nodesize2(TreeNode root) {
if (root == null) {
return 0;
}
return Nodesize2(root.left) + Nodesize2(root.rigth) + 1;
}
// 获取叶子节点的个数
//依旧两种方法
//第一遍种历二叉树
public int getLeafNodeCount;
public void getLeafNodeCount1(TreeNode root) {
if (root == null) {
return;
}
if (root.left == null && root.rigth == null) {
getLeafNodeCount++;
}
getLeafNodeCount1(root.left);
getLeafNodeCount1(root.rigth);
}
//第二种获取叶子节点个数
//左子树叶子+右子树叶子节点个数
public int getGetLeafNodeCount2(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.rigth == null) {
return 1;
}
return getGetLeafNodeCount2(root.left) + getGetLeafNodeCount2(root.rigth);
}
// 获取第K层节点的个数
public int getKLevelNodeCount(TreeNode root, int k) {
//统计k层的节点个数,先找到第k-1层
if (k == 1) {
if (root == null) {
return 0;
} else {
return 1;
}
}
return getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.rigth, k - 1);
}
// 获取二叉树的高度
public int getHeight(TreeNode root) {
//二叉树的高度等于左子树高度和右子树高度的最大值
if(root==null) {
return 0;
}
return getHeight(root.left)>getHeight(root.rigth)?getHeight(root.left)+1:getHeight(root.rigth)+1;
}
// 检测值为value的元素是否存在
public TreeNode find(TreeNode root, int val){
if(root.val==val){
return root;
}
if(root.left!=null){
TreeNode leftT=find(root.left,val);
if(leftT!=null){
return leftT;
}
}
if(root.rigth!=null){
TreeNode rigthT =find(root.rigth,val);
if(rigthT!=null){
return rigthT;
}
}
return null;
}
//层序遍历
public void levelOrder(TreeNode root){
if(root==null){
return;
}
//创建一个队列
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
TreeNode cur=root;
while(!queue.isEmpty()){
//先将队列元素弹出
cur=queue.poll();
System.out.print(cur.val+" ");
if(cur.left!=null){
queue.offer(cur.left);
}
if(cur.rigth!=null){
queue.offer(cur.rigth);
}
}
System.out.println();
}
// 判断一棵树是不是完全二叉树
public boolean isCompleteTree(TreeNode root){
//完全二叉树从左到右依次填满
Queue<TreeNode> queue=new LinkedList<>();
TreeNode cur=root;
queue.offer(cur);
while(queue.peek()!=null){
cur=queue.poll();
queue.offer(cur.left);
queue.offer(cur.rigth);
}
while(!queue.isEmpty()){
if(queue.poll()!=null){
return false;
}
}
return true;
}
public static void main(String[] args) {
}
}
Test类
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Test {
public static void main(String[] args) {
//调用创建二叉树
BinaryTree binaryTree=new BinaryTree();
//调用类中2的成员变量
BinaryTree.TreeNode root=binaryTree.createbinarytree();
//测试前序遍历打印
binaryTree.preOrder(root);
System.out.println();
// //测试前序遍历打印1
binaryTree.preOrder1(root);
System.out.println();
System.out.println("==============================");
System.out.println();
//测试中序遍历
binaryTree.inOrder(root);
System.out.println();
//测试中序遍历的非递归方法
binaryTree.inOrder1(root);
System.out.println("===============================");
System.out.println();
//测试后序遍历
binaryTree.postOrder(root);
System.out.println();
//测试后续遍历的非递归方法
binaryTree.postOrder1(root);
System.out.println("=========================");
System.out.println();
//打印节点个数
binaryTree.Nodesize1(root);
System.out.println("二叉树结点个数:"+binaryTree.nodesize);
//测试第二种打印节点个数方法
System.out.println("二叉树结点个数:"+binaryTree.Nodesize2(root));
//测试第一种叶子节点个数
binaryTree.getLeafNodeCount1(root);
System.out.println("二叉树叶子节点个数:"+binaryTree.getLeafNodeCount);
//测试第二种叶子节点个数
System.out.println("二叉树叶子节点个数:"+binaryTree.getGetLeafNodeCount2(root));
//测试统计第k层的节点个数
System.out.println("第1层的节点个数为"+binaryTree.getKLevelNodeCount(root, 1));
System.out.println("第2层的节点个数为"+binaryTree.getKLevelNodeCount(root, 2));
System.out.println("第3层的节点个数为"+binaryTree.getKLevelNodeCount(root, 3));
System.out.println("第4层的节点个数为"+binaryTree.getKLevelNodeCount(root, 4));
//测试计算二叉树的高度
System.out.println("二叉树的高度为:"+binaryTree.getHeight(root));
//测试二叉树的是否存在的元素
List<List<Integer>> llist=new ArrayList<List<Integer>>();
List<Integer> list1=new ArrayList<>();
llist.add(list1);
Queue<Integer> queue=new LinkedList<>();
queue.offer(1);
System.out.println(queue.poll());
System.out.println("元素1存在节点"+binaryTree.find(root, 1)+"中");
System.out.println("元素4存在节点"+binaryTree.find(root, 4)+"中");
System.out.println("元素8存在节点"+binaryTree.find(root, 8)+"中");
System.out.println("元素7存在节点"+binaryTree.find(root, 7)+"中");
//测试二叉树的层序遍历
binaryTree.levelOrder(root);
//判断二叉树是否是完全二叉树
System.out.println("判断这棵树是否是完全二叉树 "+binaryTree.isCompleteTree(root));
}
}
3.优先队列(PriorityQueue)
3.1堆的性质
堆逻辑上是一颗完全二叉树,物理上是存储在数组中
总结:一颗完全二叉树以层序遍历方式放入数组中存储,这种方式的主要用法就是堆的表示。
关于堆下标位置的计算
如果已知父亲(parent) 的下标,
则: 左孩子(left) 下标 = 2 * parent + 1;
右孩子(right) 下标 = 2 * parent + 2;
已知孩子(不区分左右)(child)下标,
则: 双亲(parent) 下标 = (child - 1) / 2;
3.2堆的分类
大堆:根节点大于左右两个子节点的完全二叉树 (父亲节点大于其子节点),叫做大堆,或者大根堆,或者最大堆 。
小堆:根节点小于左右两个子节点的完全二叉树叫 小堆(父亲节点小于其子节点),或者小根堆,或者最小堆。
3.3堆的底层代码实现
创建小根堆
创建大根堆
向上调整
现在有一个堆,我们需要在堆的末尾插入数据,再对其进行调整,使其仍然保持堆的结构,这就是向上调整。
以大堆为例:
向下调整
堆的删除和添加
PriorityQueue类
import java.lang.reflect.Array;
import java.util.Arrays;
public class PriorityQueue {
public int[] elem;
public int usedsize;
public PriorityQueue() {
this.elem=new int[10];
}
public void initelem(int [] array){
for(int i=0;i<array.length;i++){
this.elem[i]=array[i];
usedsize++;
}
}
public void elempeek(){
for(int i=0;i<this.usedsize;i++){
System.out.print(elem[i]+" ");
}
}
//创建小根堆
public void Smallrootpiles(){
//小根堆的节点数要大于子树
//第一步先确定有多少个树,利用最后一个节点来找到父亲节点
for(int parent=(usedsize-1-1)/2;parent>=0;parent--){
//也是一样采用向下调整的方法
siftDown2(parent,usedsize);
}
}
public void siftDown2(int parent,int usedsize){
//parent:代表调整子树的起始位置
//usedsize:判断子树是否还需要调整
//1.根据父亲节点,找到左子树孩子节点
int child=parent*2+1;
//2.找到孩子之间的最小值
while(child<usedsize){
if(child+1<usedsize&&elem[child]>elem[child+1]){
child++;
}
if(elem[child]<elem[parent]){
int num=elem[parent];
elem[parent]=elem[child];
elem[child]=num;
parent=child;
child=child*2+1;
}else{
break;
}
}
}
//创建大根堆
public void createhead(){
//大根堆的节点数要大于子树
//第一步先确定有多少个树,利用最后一个节点来找到父亲节点
for(int parent=(usedsize-1-1)/2;parent>=0;parent--){
//采用向下调整
siftDown1(parent,usedsize);
}
}
//parent:代表调整子树的起始位置
//usedsize:判断子树是否还需要调整
public void siftDown1(int parent,int usedsize){
//根据父亲节点找到左子树节点
int child=parent*2+1;
while(child<usedsize){
if(child+1<usedsize&&elem[child]<elem[child+1]){
child++;
}
if(elem[child]>elem[parent]){
int num=elem[parent];
elem[parent]=elem[child];
elem[child]=num;
// elem[child]=elem[child]^elem[parent];
// elem[parent]=elem[child]^elem[parent];
// elem[child]=elem[child]^elem[child];
parent=child;
child=child*2+1;
}else{
break;
}
}
}
//向堆中增加元素
public void push(int val) {
//先进行判满处理
if (isFull()) {
//进行扩容
elem = Arrays.copyOf(elem, elem.length * 2);
}
elem[usedsize] = val;
usedsize++;
siftUp(usedsize - 1);
}
//向上调整
public void siftUp(int child) {
//孩子节点找到父亲节点
int parent=(child-1)/2;
//向上调整
while(parent>=0){
if(elem[child]>elem[parent]){
int num=elem[parent];
elem[parent]=elem[child];
elem[child]=num;
child=parent;
parent=(parent-1)/2;
}else{
break;
}
}
}
public boolean isFull(){
if(usedsize==this.elem.length){
return true;
}
return false;
}
//删除堆的元素
public int poll(){
if(isEmpty()){
return -1;
}
int val=elem[0];
//交换0位置和最后一个节点位置
elem[0]=elem[usedsize-1];
elem[usedsize-1]=val;
//对0节点位置进行向下调整
siftDown1(0,usedsize-1);
usedsize--;
return val;
}
//判段是否为空
public boolean isEmpty(){
if (usedsize==0){
return true;
}
return false;
}
//对节点进行排从小到大排序(再节点本身上排序)
public void headsort(){
//先对数据进行建大堆
this.createhead();
int end=usedsize-1;
while(end>0){
int num=elem[0];
elem[0]=elem[end];
elem[end]=num;
siftDown1(0,end);
end--;
}
}
public static void main(String[] args) {
}
}
Test类
public class Test {
public static void main(String[] args) {
//对队进行初始化
PriorityQueue priorityQueue=new PriorityQueue();
int[] array={ 27,15,19,18,28,34,65,49,25,37 };
priorityQueue.initelem(array);
//测试建立小根堆
priorityQueue.Smallrootpiles();
priorityQueue.elempeek();
System.out.println();
//测试大根堆的实现
priorityQueue.createhead();
priorityQueue.elempeek();
//测试插入一个元素
priorityQueue.push(80);
System.out.println();
priorityQueue.elempeek();
//测试删除一个元素
priorityQueue.poll();
System.out.println();
priorityQueue.elempeek();
System.out.println();
//测试堆排序
priorityQueue.headsort();
priorityQueue.elempeek();
//
}
}
3.4堆的问题
3.4.1topk问题
给你6个数据,求前3个最大数据。这时候我们用堆怎么做的?
解题思路:
1、如果求前K个最大的元素,要建一个小根堆。
2、如果求前K个最小的元素,要建一个大根堆。
3、第K大的元素。建一个小堆,堆顶元素就是第K大的元素。
4、第K小的元素。建一个大堆,堆顶元素就是第K大的元素。
3.4.2数组的排序
再者说如果要对一个数组进行从小到大排序,要借助大根堆还是小根堆呢?
---->大根堆
3.5PriorityQueue
使用注意:
1. 使用时必须导入PriorityQueue所在的包,即:
import java.util.PriorityQueue;
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常
3. 不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为
6. PriorityQueue底层使用了堆数据结构
7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
1.优先级队列的构造
static void TestPriorityQueue(){
// 1.创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 2.创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(3);
list.add(2);
list.add(1);
// 用ArrayList对象来构造一个优先级队列的对象
// q3中已经包含了三个元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
System.out.println(q3.size());
System.out.println(q3.peek());
}
//注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
p.offer(4);
p.offer(3);
p.offer(2);
p.offer(1);
p.offer(5)
自定义排序规则
1.该改变升序降序
2.根据HashMap的value值对HashMap的键值对排序
方法摘要
System.out.println(p.peek());
}
}
static void TestPriorityQueue2(){
int[] arr = {4,1,9,2,8,0,7,3,6,5};
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
for (int e: arr) {
q.offer(e);
}
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
q.poll();
q.poll();
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
q.offer(0);
System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
q.clear();
if(q.isEmpty()){
System.out.println("优先级队列已经为空!!!")
}
else{
System.out.println("优先级队列不为空");
}
}