Home  >  Article  >  Java  >  Detailed graphic explanation of Java data structures and algorithms

Detailed graphic explanation of Java data structures and algorithms

WBOY
WBOYforward
2022-03-21 17:35:112369browse

This article brings you relevant knowledge about java, which mainly introduces java code implementation, annotation analysis, algorithm analysis and other related issues. I hope the detailed explanations in pictures and texts will be helpful to everyone.

Detailed graphic explanation of Java data structures and algorithms

## Recommended study: "

java Learning Tutorial"

Chapter 1 Overview of Data Structure and Algorithm Basics

1.1 The importance of data structures and algorithms

  • ##Algorithms are the soul of programs. Excellent programs can still maintain high-speed calculations when calculating massive amounts of data

  • The relationship between data structure and algorithm

    :

    Program = data structure algorithm
    • The data structure is the basis of the algorithm, in other words, If you want to learn algorithms well, you need to know the data structure well.
  • Data structure and algorithm learning outline


    Detailed graphic explanation of Java data structures and algorithms

  • 1.2 Data structure overview

Data structure can be simply understood as some relationships between data. The structure of data is divided into the storage structure of data and the logical structure of data.

Detailed graphic explanation of Java data structures and algorithmsLogical structure

    Set structure
  • : Data elements belong to the same set, and they are juxtaposed Relationship, no other relationship; it can be understood as a collection of learning in middle school. Within a range, there are many elements, and there is no relationship between elements.
  • Linear structure
  • : between elements There is a one-to-one relationship; it can be understood that each student corresponds to a student number, and the student number and name are linear structures
  • Tree structure
  • : There is a pair between elements The relationship of many can be simply understood as a family genealogy, one generation after another.
  • Graphic structure
  • : There is a many-to-many relationship between elements. Each element may correspond to multiple elements. Or corresponding to multiple elements, network graph
  • Storage structure

    Sequential storage structure
  • : It means to store data continuously, we can Compare it to queuing up for meals in a school cafeteria, one after another;
  • Chained storage structure
  • : It is not stored in order. The next number that comes in only needs to tell its address. The previous node, the address of the number behind it is stored in the previous node, so the storage address of the last number is null; this structure can be compared to calling a number at a shopping mall, and the number above is compared to an address. You What is the subsequent address? The other content above is the content of the node;
  • Difference
  • : The characteristic of sequential storage is that querying is fast and insertion or deletion is slow.
    • The characteristics of chain storage are slow query and fast insertion or deletion
  • 1.3 Algorithm Overview

The same problem is solved in different ways Method
  • Judge the quality of the algorithm through time and space complexity
  • There is no best algorithm, only the most appropriate. Learning algorithms is to accumulate learning ideas and master learning ideas, not to Solve a certain problem to remember a certain algorithm; regarding time complexity and space complexity, in most development situations now, we are using space for time, consuming more memory to ensure faster speed.
  • Complexity and stability of each sorting algorithm
  • :
    Detailed graphic explanation of Java data structures and algorithms
  • How to understand "Big O notation"

Particularly specific and detailed analysis of algorithms, while great, has limited practical value in practice. For the temporal and spatial properties of the algorithm, the most important thing is its magnitude and trend, which are the main part of analyzing the efficiency of the algorithm. The constant factors in the size function that measure the number of basic operations of the algorithm can be ignored. For example, it can be considered that 3n^2 and 100n^2 belong to the same order of magnitude. If the cost of two algorithms processing instances of the same size is these two functions, their efficiency is considered to be "similar", and both are n^2 level.

Time complexity

The time an algorithm takes is proportional to the number of executions of statements in the algorithm. Which algorithm has more statements, it takes more time. The number of statement executions in the algorithm is called statement frequency or time frequency, recorded as

T(n)

. n is called the scale of the problem. When n continues to change, the time frequency T(n) will also continue to change. But sometimes we want to know what pattern it shows when it changes. To this end, we introduce the concept of Time Complexity.

Generally, the number of times the basic operations in the algorithm are repeated is a function of the problem size n, represented by T(n). If there is an auxiliary function f(n) , so that when n approaches infinitely large, the limit value of T(n)/f(n) is a constant not equal to zero, then it is called f(n) is a function of the same order of magnitude as T(n). Denoted as T(n)=O(f(n)), we call O(f(n)) the asymptotic time complexity of the algorithm, abbreviated as time complexity.

Sometimes, the number of times the basic operations in the algorithm are repeated varies with the input data set of the problem. For example, in bubble sorting, the input data is ordered but not ordered, and the results are different. At this point, we calculate the average.

Basic calculation rules for time complexity:

    Basic operations, that is, only constant terms, are considered to have a time complexity of O(1)
  • Sequential structure, time complexity is calculated according to
  • addition
  • Loop structure, time complexity is calculated according to
  • multiplication
  • Branch structure, time Complexity
  • Take the maximum value
  • When judging the efficiency of an algorithm, you often only need to pay attention to the highest order term of the number of operations. Other minor terms and constant terms can be ignored
  • Unless otherwise specified, the time complexity of the algorithms we analyze refers to
  • worst time complexity

common time complexity:
Detailed graphic explanation of Java data structures and algorithms

  • Note: Log2n (logarithm with base 2) is often abbreviated as logn

Common The relationship between time complexity:

Detailed graphic explanation of Java data structures and algorithms

  • So the time consumption from small to large is:O(1 )

Case 1:

count = 0;				      (1)
	for(i = 0;i 

    statement (1) executes 1
  • statement (2) Execute the
  • statement n times (3) Execute the
  • statement n^2 times (4) Execute the
  • statement n^2 times. The time complexity is : T(n) = 1 n n^2 n^2 = O(n^2)
##Case 2

:

a = 1; 						(1)
b = 2;						(2)
for(int i = 1;i 

Statement (1), (2) is executed once
  • Statement (3) is executed n times
  • Statement (4), (5), (6) is executed n times
  • The time complexity is
  • : T(n) = 1 1 4n = o(n)
  • ##Case 3

i = 1;		 (1)while(i<n i><strong></strong>The frequency of statement (1) is 1<ul>Suppose the frequency of statement (2) is<li>f(n)</li>, then <li>2f(n), take the maximum value f(n) = log2n<code></code><code></code>The time complexity is </li>:<li>T(n) = O(log2n)<strong></strong><code></code>Space complexity</li>
</ul>
<h3></h3>The space complexity calculation formula of the algorithm : <ul>S(n) = 0( f(n) )<li>, where n is the input size, <p>f(n)<code> is the function of the storage space occupied by n in the statement</code><code></code></p>The storage space occupied by an algorithm on the computer memory includes three aspects</li>
<li>
<p>The storage space occupied by the storage algorithm itself</p>
<ul>Input of the algorithm The storage space occupied by the output data<li>The storage space temporarily occupied by the algorithm during operation<li>
<li>
</ul>
</li>Case</ul>: The specified array is used Reverse and return the reversed content<p><strong>Solution 1:</strong></p>
<pre class="brush:php;toolbar:false">public static int[] reverse1(int[] arr) {
    int n = arr.length; //申请4个字节
    int temp; //申请4个字节
    for (int start = 0, end = n - 1; start <p></p>The space complexity is
    :
  • S(n) = 4 4 = O(8) = O(1) Solution 2:
public static int[] reverse2(int[] arr) {
    int n = arr.length; //申请4个字节
    int[] temp = new int[n]; //申请n*4个字节+数组自身头信息开销24个字节
    for (int i = n - 1; i >= 0; i--) {
        temp[n - 1 - i] = arr[i];
    }
    return temp;}

The space complexity is
    :
  • S( n) = 4 4n 24 = O(n 28) = O(n)According to the big O derivation rule, the space complexity of algorithm one is 0(1), and that of algorithm two is The space complexity is 0(n), so from the perspective of space occupation, algorithm one is better than algorithm two.
Since there is a memory garbage collection mechanism in Java, and the JVM also optimizes the memory usage of the program (such as just-in-time compilation), we cannot accurately evaluate the memory usage of a Java program, but we understand the basic memory of Java Occupation allows us to estimate the memory usage of java programs.

Since the memory of current computer equipment is generally relatively large, basically personal computers start with 4G, and the larger ones can reach 32G, so memory usage is generally not the bottleneck of our algorithm.

Normal situation Let’s talk about the complexity directly. The default is the time complexity of the algorithm

.

However, if the program you are doing is embedded development, especially the built-in program on some sensor devices, since the memory of these devices is very small, usually a few kb, the space complexity of the algorithm will be There are requirements, but generally those who do Java development are basically server development, and generally there is no such problem.

第2章 数组

2.1 数组概念

数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。这里我们要抽取出三个跟数组相关的关键词:线性表,连续内存空间,相同数据类型;数组具有连续的内存空间,存储相同类型的数据,正是该特性使得数组具有一个特性:随机访问。但是有利有弊,这个特性虽然使得访问数组边得非常容易,但是也使得数组插入和删除操作会变得很低效,插入和删除数据后为了保证连续性,要做很多数据搬迁工作。

查找数组中的方法有两种

  • 线性查找:线性查找就是简单的查找数组中的元素
  • 二分法查找:二分法查找要求目标数组必须是有序的。

2.2 无序数组

  • 实现类
public class MyArray {
	//声明一个数组
	private long[] arr;
	
	//有效数据的长度
	private int elements;
	
	//无参构造函数,默认长度为50
	public MyArray(){
		arr = new long[50];
	}
	
	public MyArray(int maxsize){
		arr = new long[maxsize];
	}
	
	
	//添加数据
	public void insert(long value){
		arr[elements] = value;
		elements++;
	}
	
	//显示数据
	public void display(){
		System.out.print("[");
		for(int i = 0;i = elements || index = elements || index = elements || index 
  • 优点:插入快(时间复杂度为:O(1))、如果知道下标,可以很快存储
  • 缺点:查询慢(时间复杂度为:O(n))、删除慢

2.3 有序数组

  • 实现类
public class MyOrderArray { 
	private long[] arr;
	
	private int elements;
	
	public MyOrderArray(){
		arr = new long[50];
	}
	
	public MyOrderArray(int maxsize){
		arr = new long[maxsize];
	}
	
	//添加数据
	public void insert(int value){
		int i;
		for(i = 0;i  value){
				break;
			}
		}
		for(int j = elements;j > i;j--){
			arr[j] = arr[j -1];
		}
		arr[i] = value;
		elements++;
	}
	
	
	//删除数据
	public void delete(int index){
		if(index >=elements || index = elements || index = elements || index  pow){
					return -1;
				}else{
					if(arr[middle] > value){
						//待查询的数在左边,右指针重新改变指向
						pow = middle-1;
					}else{
						//带查询的数在右边,左指针重新改变指向
						low = middle +1;
					}
				}
			}
	}}
  • 优点:查询快(时间复杂度为:O(logn)
  • 缺点:增删慢(时间复杂度为:O(n)

第三章 栈

3.1 栈概念

(stack),有些地方称为堆栈,是一种容器,可存入数据元素、访问元素、删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。

由于栈数据结构只允许在一端进行操作,因而按照后进先出的原理运作。

栈可以用顺序表实现,也可以用链表实现。

Detailed graphic explanation of Java data structures and algorithms

3.2 栈的操作

  • Stack() 创建一个新的空栈
  • push(element) 添加一个新的元素element到栈顶
  • pop() 取出栈顶元素
  • peek() 返回栈顶元素
  • is_empty() 判断栈是否为空
  • size() 返回栈的元素个数

实现类

package mystack;public class MyStack {

    //栈的底层使用数组来存储数据
    //private int[] elements;
    int[] elements; //测试时使用

    public MyStack() {
        elements = new int[0];
    }

    //添加元素
    public void push(int element) {
        //创建一个新的数组
        int[] newArr = new int[elements.length + 1];
        //把原数组中的元素复制到新数组中
        for (int i = 0; i <p><strong>测试类</strong>:</p><pre class="brush:php;toolbar:false">package mystack;public class Demo {
    public static void main(String[] args) {
        MyStack ms = new MyStack();
        //添加元素
        ms.push(9);
        ms.push(8);
        ms.push(7);

        //取出栈顶元素//        System.out.println(ms.pop()); //7//        System.out.println(ms.pop()); //8//        System.out.println(ms.pop()); //9

        //查看栈顶元素
        System.out.println(ms.peek()); //7
        System.out.println(ms.peek()); //7

        //查看栈中元素个数
        System.out.println(ms.size()); //3
    }}

第4章 队列

4.1 队列概念

队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出的(First In First Out)的线性表,简称FIFO。允许插入的一端为队尾,允许删除的一端为队头。队列不允许在中间部位进行操作!假设队列是q=(a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从a1开始,而插入时,总是在队列最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后。

同栈一样,队列也可以用顺序表或者链表实现。

Detailed graphic explanation of Java data structures and algorithms

4.2 队列的操作

  • Queue() 创建一个空的队列
  • enqueue(element) 往队列中添加一个element元素
  • dequeue() 从队列头部删除一个元素
  • is_empty() 判断一个队列是否为空
  • size() 返回队列的大小

实现类

public class MyQueue {

    int[] elements;

    public MyQueue() {
        elements = new int[0];
    }

    //入队
    public void enqueue(int element) {
        //创建一个新的数组
        int[] newArr = new int[elements.length + 1];
        //把原数组中的元素复制到新数组中
        for (int i = 0; i <p><strong>测试类</strong>:</p><pre class="brush:php;toolbar:false">public class Demo {
    public static void main(String[] args) {
        MyQueue mq = new MyQueue();

        //入队
        mq.enqueue(1);
        mq.enqueue(2);
        mq.enqueue(3);

        //出队
        System.out.println(mq.dequeue()); //1
        System.out.println(mq.dequeue()); //2
        System.out.println(mq.dequeue()); //3
    }}

第5章 链表

5.1 单链表

单链表概念

单链表也叫单向链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

Detailed graphic explanation of Java data structures and algorithms

  • 表元素域data用来存放具体的数据。
  • 链接域next用来存放下一个节点的位置

单链表操作

  • is_empty() 链表是否为空
  • length() 链表长度
  • travel() 遍历整个链表
  • add(item) 链表头部添加元素
  • append(item) 链表尾部添加元素
  • insert(pos, item) 指定位置添加元素
  • remove(item) 删除节点
  • search(item) 查找节点是否存在

实现类

//一个节点public class Node {

    //节点内容
    int data;
    //下一个节点
    Node next;

    public Node(int data) {
        this.data = data;
    }

    //为节点追加节点
    public Node append(Node node) {
        //当前节点
        Node currentNode = this;
        //循环向后找
        while (true) {
            //取出下一个节点
            Node nextNode = currentNode.next();
            //如果下一个节点为null,当前节点已经是最后一个节点
            if (nextNode == null) {
                break;
            }
            //赋给当前节点,无线向后找
            currentNode = nextNode;
        }
        //把需要追加的节点,追加为找到的当前节点(最后一个节点)的下一个节点
        currentNode.next = node;
        return this;
    }

    //显示所有节点信息
    public void show() {
        Node currentNode = this;
        while (true) {
            System.out.print(currentNode.data + " ");
            //取出下一个节点
            currentNode = currentNode.next;
            //如果是最后一个节点
            if (currentNode == null) {
                break;
            }
        }
        System.out.println();
    }

    //插入一个节点作为当前节点的下一个节点
    public void after(Node node) {
        //取出下一个节点,作为下下一个节点
        Node nextNext = next;
        //把新节点作为当前节点的下一个节点
        this.next = node;
        //把下下一个节点设置为新节点的下一个节点
        node.next = nextNext;

    }

    //删除下一个节点
    public void removeNode() {
        //取出下下一个节点
        Node newNext = next.next;
        //把下下一个节点设置为当前节点的下一个节点
        this.next = newNext;
    }

    //获取下一个节点
    public Node next() {
        return this.next;
    }

    //获取节点中的数据
    public int getData() {
        return this.data;
    }

    //判断节点是否为最后一个节点
    public boolean isLast() {
        return next == null;
    }}

测试类

public class Demo {
    public static void main(String[] args) {
        //创建节点
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        //追加节点
        n1.append(n2).append(n3);
        //取出下一个节点数据
        System.out.println(n1.next().next().getData()); //3
        //判断节点是否为最后一个节点
        System.out.println(n1.isLast()); //false
        System.out.println(n1.next().next().isLast()); //true
        //显示所有节点信息
        n1.show(); //1 2 3
        //删除一个节点//        n1.next.removeNode();//        n1.show(); //1 2
        //插入一个新节点
        n1.next.after(new Node(0));
        n1.show(); //1 2 0 3
    }}

5.2 循环链表

循环链表概念

单链表的一个变形是单向循环链表,链表中最后一个节点的 next 域不再为 None,而是指向链表的头节点。
Detailed graphic explanation of Java data structures and algorithms

循环链表操作

实现类

//表示一个节点public class LoopNode {

    //节点内容
    int data;
    //下一个节点
    LoopNode next = this; //与单链表的区别,追加了一个this,当只有一个节点时指向自己

    public LoopNode(int data) {
        this.data = data;
    }

    //插入一个节点
    public void after(LoopNode node) {
        LoopNode afterNode = this.next;
        this.next = node;
        node.next = afterNode;
    }

    //删除一个节点
    public void removeNext() {
        LoopNode newNode = this.next.next;
        this.next = newNode;
    }

    //获取下一个节点
    public LoopNode next() {
        return this.next;
    }

    //获取节点中的数据
    public int getData() {
        return this.data;
    }}

测试类

public class Demo {
    public static void main(String[] args) {
        //创建节点
        LoopNode n1 = new LoopNode(1);
        LoopNode n2 = new LoopNode(2);
        LoopNode n3 = new LoopNode(3);
        LoopNode n4 = new LoopNode(4);

        //增加节点
        n1.after(n2);
        n2.after(n3);
        n3.after(n4);
        System.out.println(n1.next().getData()); //2
        System.out.println(n2.next().getData()); //3
        System.out.println(n3.next().getData()); //4
        System.out.println(n4.next().getData()); //1
    }}

5.3 双向循环链表

双向循环链表概念

在双向链表中有两个指针域,一个是指向前驱结点的prev,一个是指向后继结点的next指针
Detailed graphic explanation of Java data structures and algorithms

双向循环链表操作

实现类

public class DoubleNode {
    //上一个节点
    DoubleNode pre = this;
    //下一个节点
    DoubleNode next = this;
    //节点数据
    int data;

    public DoubleNode(int data) {
        this.data = data;
    }

    //增加节点
    public void after(DoubleNode node) {
        //原来的下一个节点
        DoubleNode nextNext = next;
        //新节点作为当前节点的下一个节点
        this.next = node;
        //当前节点作为新节点的前一个节点
        node.pre = this;
        //原来的下一个节点作为新节点的下一个节点
        node.next = nextNext;
        //原来的下一个节点的上一个节点为新节点
        nextNext.pre = node;
    }

    //获取下一个节点
    public DoubleNode getNext() {
        return this.next;
    }

    //获取上一个节点
    public DoubleNode getPre() {
        return this.pre;
    }

    //获取数据
    public int getData() {
        return this.data;
    }}

测试类

public class Demo {
    public static void main(String[] args) {
        //创建节点
        DoubleNode n1 = new DoubleNode(1);
        DoubleNode n2 = new DoubleNode(2);
        DoubleNode n3 = new DoubleNode(3);

        //追加节点
        n1.after(n2);
        n2.after(n3);

        //查看上一个,自己,下一个节点内容
        System.out.println(n2.getPre().getData()); //1
        System.out.println(n2.getData()); //2
        System.out.println(n2.getNext().getData()); //3

        System.out.println(n1.getPre().getData()); //3
        System.out.println(n3.getNext().getData()); //1
    }}

第6章 树结构基础

6.1 为什么要使用树结构

线性结构中不论是数组还是链表,他们都存在着诟病;比如查找某个数必须从头开始查,消耗较多的时间。使用树结构,在插入查找的性能上相对都会比线性结构要好

6.2 树结构基本概念

示意图
Detailed graphic explanation of Java data structures and algorithms
1、根节点:最顶上的唯一的一个;如:A

2、双亲节点:子节点的父节点就叫做双亲节点;如A是B、C、D的双亲节点,B是E、F的双亲节点

3、子节点:双亲节点所产生的节点就是子节点

4、路径:从根节点到目标节点所走的路程叫做路径;如A要访问F,路径为A-B-F

5、节点的度:有多少个子节点就有多少的度(最下面的度一定为0,所以是叶子节点)

6、节点的权:在节点中所存的数字

7、叶子节点:没有子节点的节点,就是没有下一代的节点;如:E、F、C、G

8、子树:在整棵树中将一部分看成也是一棵树,即使只有一个节点也是一棵树,不过这个树是在整个大树职中的,包含的关系

9、:就是族谱中有多少代的人;如:A是1,B、C、D是2,E、F、G是3

10、树的高度:树的最大的层数:就是层数中的最大值

11、森林:多个树组成的集合

6.3 树的种类

无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;

有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;

  • 二叉树:每个节点最多含有两个子树的树称为二叉树;
    • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树;
    • 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
    • 排序二叉树(二叉查找树(英语:Binary Search Tree),也称二叉搜索树、有序二叉树);
  • 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树;
  • B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树。

6.4 树的存储与表示

顺序存储:将数据结构存储在固定的数组中,然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。
Detailed graphic explanation of Java data structures and algorithms
链式存储
Detailed graphic explanation of Java data structures and algorithms
由于对节点的个数无法掌握,常见树的存储表示都转换成二叉树进行处理,子节点个数最多为2

6.5 常见的一些树的应用场景

1、xml,html等,那么编写这些东西的解析器的时候,不可避免用到树

2、路由协议就是使用了树的算法

3、mysql数据库索引

4、文件系统的目录结构

5、所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构

第7章 二叉树大全

7.1 二叉树的定义

任何一个节点的子节点数量不超过 2,那就是二叉树;二叉树的子节点分为左节点和右节点,不能颠倒位置

7.2 二叉树的性质(特性)

性质1:在二叉树的第i层上至多有2^(i-1)个结点(i>0)

性质2:深度为k的二叉树至多有2^k - 1个结点(k>0)

性质3:对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;

性质4:具有n个结点的完全二叉树的深度必为 log2(n+1)

性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)

7.3 满二叉树与完全二叉树

满二叉树: 所有叶子结点都集中在二叉树的最下面一层上,而且结点总数为:2^n-1 (n为层数 / 高度)

完全二叉树: 所有的叶子节点都在最后一层或者倒数第二层,且最后一层叶子节点在左边连续,倒数第二层在右边连续(满二叉树也是属于完全二叉树)(从上往下,从左往右能挨着数满)
Detailed graphic explanation of Java data structures and algorithms

7.4 链式存储的二叉树

创建二叉树:首先需要一个树的类,还需要另一个类用来存放节点,设置节点;将节点放入树中,就形成了二叉树;(节点中需要权值,左子树,右子树,并且都能对他们的值进行设置)。

树的遍历

  • 先序遍历:根节点,左节点,右节点(如果节点有子树,先从左往右遍历子树,再遍历兄弟节点)
    先序遍历结果为:A B D H I E J C F K G

Detailed graphic explanation of Java data structures and algorithms

  • 中序遍历:左节点,根节点,右节点(中序遍历可以看成,二叉树每个节点,垂直方向投影下来(可以理解为每个节点从最左边开始垂直掉到地上),然后从左往右数)
    中遍历结果为:H D I B E J A F K C G

Detailed graphic explanation of Java data structures and algorithms

  • 后序遍历:左节点,右节点,根节点
    后序遍历结果:H I D J E B K F G C A

Detailed graphic explanation of Java data structures and algorithms

  • 层次遍历:从上往下,从左往右
    层次遍历结果:A B C D E F G H I J K
    Detailed graphic explanation of Java data structures and algorithms

查找节点:先对树进行一次遍历,然后找出要找的那个数;因为有三种排序方法,所以查找节点也分为先序查找,中序查找,后序查找;

删除节点:由于链式存储,不能找到要删的数直接删除,需要找到他的父节点,然后将指向该数设置为null;所以需要一个变量来指向父节点,找到数后,再断开连接。

代码实现
Detailed graphic explanation of Java data structures and algorithms

  • 树类
public class BinaryTree {

    TreeNode root;

    //设置根节点
    public void setRoot(TreeNode root) {
        this.root = root;
    }

    //获取根节点
    public TreeNode getRoot() {
        return root;
    }

    //先序遍历
    public void frontShow() {
        if (root != null) {
            root.frontShow();
        }
    }

    //中序遍历
    public void middleShow() {
        if (root != null) {
            root.middleShow();
        }
    }

    //后序遍历
    public void afterShow() {
        if (root != null) {
            root.afterShow();
        }
    }

    //先序查找
    public TreeNode frontSearch(int i) {
        return root.frontSearch(i);
    }

    //删除一个子树
    public void delete(int i) {
        if (root.value == i) {
            root = null;
        } else {
            root.delete(i);
        }
    }}
  • 节点类
public class TreeNode {
    //节点的权
    int value;
    //左儿子
    TreeNode leftNode;
    //右儿子
    TreeNode rightNode;

    public TreeNode(int value) {
        this.value = value;
    }

    //设置左儿子
    public void setLeftNode(TreeNode leftNode) {
        this.leftNode = leftNode;
    }

    //设置右儿子
    public void setRightNode(TreeNode rightNode) {
        this.rightNode = rightNode;
    }

    //先序遍历
    public void frontShow() {
        //先遍历当前节点的值
        System.out.print(value + " ");
        //左节点
        if (leftNode != null) {
            leftNode.frontShow(); //递归思想
        }
        //右节点
        if (rightNode != null) {
            rightNode.frontShow();
        }
    }

    //中序遍历
    public void middleShow() {
        //左节点
        if (leftNode != null) {
            leftNode.middleShow(); //递归思想
        }
        //先遍历当前节点的值
        System.out.print(value + " ");
        //右节点
        if (rightNode != null) {
            rightNode.middleShow();
        }
    }

    //后续遍历
    public void afterShow() {
        //左节点
        if (leftNode != null) {
            leftNode.afterShow(); //递归思想
        }
        //右节点
        if (rightNode != null) {
            rightNode.afterShow();
        }
        //先遍历当前节点的值
        System.out.print(value + " ");
    }

    //先序查找
    public TreeNode frontSearch(int i) {
        TreeNode target = null;
        //对比当前节点的值
        if (this.value == i) {
            return this;
            //当前节点不是要查找的节点
        } else {
            //查找左儿子
            if (leftNode != null) {
                //查找的话t赋值给target,查不到target还是null
                target = leftNode.frontSearch(i);
            }
            //如果target不为空,说明在左儿子中已经找到
            if (target != null) {
                return target;
            }
            //如果左儿子没有查到,再查找右儿子
            if (rightNode != null) {
                target = rightNode.frontSearch(i);
            }
        }
        return target;
    }

    //删除一个子树
    public void delete(int i) {
        TreeNode parent = this;
        //判断左儿子
        if (parent.leftNode != null && parent.leftNode.value == i) {
            parent.leftNode = null;
            return;
        }
        //判断右儿子
        if (parent.rightNode != null && parent.rightNode.value == i) {
            parent.rightNode = null;
            return;
        }
        //如果都不是,递归检查并删除左儿子
        parent = leftNode;
        if (parent != null) {
            parent.delete(i);
        }
        //递归检查并删除右儿子
        parent = rightNode;
        if (parent != null) {
            parent.delete(i);
        }

    }}
  • 测试类
public class Demo {
    public static void main(String[] args) {
        //创建一棵树
        BinaryTree binaryTree = new BinaryTree();
        //创建一个根节点
        TreeNode root = new TreeNode(1);
        //把根节点赋给树
        binaryTree.setRoot(root);
        //创建左,右节点
        TreeNode rootLeft = new TreeNode(2);
        TreeNode rootRight = new TreeNode(3);
        //把新建的节点设置为根节点的子节点
        root.setLeftNode(rootLeft);
        root.setRightNode(rootRight);
        //为第二层的左节点创建两个子节点
        rootLeft.setLeftNode(new TreeNode(4));
        rootLeft.setRightNode(new TreeNode(5));
        //为第二层的右节点创建两个子节点
        rootRight.setLeftNode(new TreeNode(6));
        rootRight.setRightNode(new TreeNode(7));

        //先序遍历
        binaryTree.frontShow(); //1 2 4 5 3 6 7
        System.out.println();
        //中序遍历
        binaryTree.middleShow(); //4 2 5 1 6 3 7
        System.out.println();
        //后序遍历
        binaryTree.afterShow(); //4 5 2 6 7 3 1
        System.out.println();

        //先序查找
        TreeNode result = binaryTree.frontSearch(5);
        System.out.println(result); //binarytree.TreeNode@1b6d3586

        //删除一个子树
        binaryTree.delete(2);
        binaryTree.frontShow(); //1 3 6 7 ,2和他的子节点被删除了
    }}

7.5 顺序存储的二叉树

Detailed graphic explanation of Java data structures and algorithms

概述:顺序存储使用数组的形式实现;由于非完全二叉树会导致数组中出现空缺,有的位置不能填上数字,所以顺序存储二叉树通常情况下只考虑完全二叉树

原理: 顺序存储在数组中是按照第一层第二层一次往下存储的,遍历方式也有先序遍历、中序遍历、后续遍历

性质

  • 第n个元素的左子节点是:2*n+1;
  • 第n个元素的右子节点是:2*n+2;
  • 第n个元素的父节点是:(n-1)/2

代码实现

  • 树类
public class ArrayBinaryTree {

    int[] data;

    public ArrayBinaryTree(int[] data) {
        this.data = data;
    }

    //重载先序遍历方法,不用每次传参数了,保证每次从头开始
    public void frontShow() {
        frontShow(0);
    }

    //先序遍历
    public void frontShow(int index) {
        if (data == null || data.length == 0) {
            return;
        }
        //先遍历当前节点的内容
        System.out.print(data[index] + " ");
        //处理左子树:2*index+1
        if (2 * index + 1 
  • 测试类
public class Demo {
    public static void main(String[] args) {
        int[] data = {1,2,3,4,5,6,7};
        ArrayBinaryTree tree = new ArrayBinaryTree(data);
        //先序遍历
        tree.frontShow(); //1 2 4 5 3 6 7
    }}

7.6 线索二叉树(Threaded BinaryTree)

为什么使用线索二叉树?

当用二叉链表作为二叉树的存储结构时,可以很方便的找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点

原理:n个结点的二叉链表中含有n+1(2n-(n-1)=n+1个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前驱和后继结点的指针。

例如:某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某个结点的右孩子为空,则将空的右孩子指针域改为指向其后继(这种附加的指针称为"线索")
Detailed graphic explanation of Java data structures and algorithms
代码实现

  • 树类
public class ThreadedBinaryTree {

    ThreadedNode root;
    //用于临时存储前驱节点
    ThreadedNode pre = null;
    
    //设置根节点
    public void setRoot(ThreadedNode root) {
        this.root = root;
    }

    //中序线索化二叉树
    public void threadNodes() {
        threadNodes(root);
    }

    public void threadNodes(ThreadedNode node) {
        //当前节点如果为null,直接返回
        if (node == null) {
            return;
        }
        //处理左子树
        threadNodes(node.leftNode);

        //处理前驱节点
        if (node.leftNode == null) {
            //让当前节点的左指针指向前驱节点
            node.leftNode = pre;
            //改变当前节点左指针类型
            node.leftType = 1;
        }
        //处理前驱的右指针,如果前驱节点的右指针是null(没有右子树)
        if (pre != null && pre.rightNode == null) {
            //让前驱节点的右指针指向当前节点
            pre.rightNode = node;
            //改变前驱节点的右指针类型
            pre.rightType = 1;
        }

        //每处理一个节点,当前节点是下一个节点的前驱节点
        pre = node;

        //处理右子树
        threadNodes(node.rightNode);
    }

    //遍历线索二叉树
    public void threadIterate() {
        //用于临时存储当前遍历节点
        ThreadedNode node = root;
        while (node != null) {
            //循环找到最开始的节点
            while (node.leftType == 0) {
                node = node.leftNode;
            }
            //打印当前节点的值
            System.out.print(node.value + " ");
            //如果当前节点的右指针指向的是后继节点,可能后继节点还有后继节点
            while (node.rightType == 1) {
                node = node.rightNode;
                System.out.print(node.value + " ");
            }
            //替换遍历的节点
            node = node.rightNode;

        }
    }

    //获取根节点
    public ThreadedNode getRoot() {
        return root;
    }

    //先序遍历
    public void frontShow() {
        if (root != null) {
            root.frontShow();
        }
    }

    //中序遍历
    public void middleShow() {
        if (root != null) {
            root.middleShow();
        }
    }

    //后序遍历
    public void afterShow() {
        if (root != null) {
            root.afterShow();
        }
    }

    //先序查找
    public ThreadedNode frontSearch(int i) {
        return root.frontSearch(i);
    }

    //删除一个子树
    public void delete(int i) {
        if (root.value == i) {
            root = null;
        } else {
            root.delete(i);
        }
    }}
  • 节点类
public class ThreadedNode {
    //节点的权
    int value;
    //左儿子
    ThreadedNode leftNode;
    //右儿子
    ThreadedNode rightNode;
    //标识指针类型,1表示指向上一个节点,0
    int leftType;
    int rightType;

    public ThreadedNode(int value) {
        this.value = value;
    }

    //设置左儿子
    public void setLeftNode(ThreadedNode leftNode) {
        this.leftNode = leftNode;
    }

    //设置右儿子
    public void setRightNode(ThreadedNode rightNode) {
        this.rightNode = rightNode;
    }

    //先序遍历
    public void frontShow() {
        //先遍历当前节点的值
        System.out.print(value + " ");
        //左节点
        if (leftNode != null) {
            leftNode.frontShow(); //递归思想
        }
        //右节点
        if (rightNode != null) {
            rightNode.frontShow();
        }
    }

    //中序遍历
    public void middleShow() {
        //左节点
        if (leftNode != null) {
            leftNode.middleShow(); //递归思想
        }
        //先遍历当前节点的值
        System.out.print(value + " ");
        //右节点
        if (rightNode != null) {
            rightNode.middleShow();
        }
    }

    //后续遍历
    public void afterShow() {
        //左节点
        if (leftNode != null) {
            leftNode.afterShow(); //递归思想
        }
        //右节点
        if (rightNode != null) {
            rightNode.afterShow();
        }
        //先遍历当前节点的值
        System.out.print(value + " ");
    }

    //先序查找
    public ThreadedNode frontSearch(int i) {
        ThreadedNode target = null;
        //对比当前节点的值
        if (this.value == i) {
            return this;
            //当前节点不是要查找的节点
        } else {
            //查找左儿子
            if (leftNode != null) {
                //查找的话t赋值给target,查不到target还是null
                target = leftNode.frontSearch(i);
            }
            //如果target不为空,说明在左儿子中已经找到
            if (target != null) {
                return target;
            }
            //如果左儿子没有查到,再查找右儿子
            if (rightNode != null) {
                target = rightNode.frontSearch(i);
            }
        }
        return target;
    }

    //删除一个子树
    public void delete(int i) {
        ThreadedNode parent = this;
        //判断左儿子
        if (parent.leftNode != null && parent.leftNode.value == i) {
            parent.leftNode = null;
            return;
        }
        //判断右儿子
        if (parent.rightNode != null && parent.rightNode.value == i) {
            parent.rightNode = null;
            return;
        }
        //如果都不是,递归检查并删除左儿子
        parent = leftNode;
        if (parent != null) {
            parent.delete(i);
        }
        //递归检查并删除右儿子
        parent = rightNode;
        if (parent != null) {
            parent.delete(i);
        }

    }}
  • 测试类
public class Demo {
    public static void main(String[] args) {
        //创建一棵树
        ThreadedBinaryTree binaryTree = new ThreadedBinaryTree();
        //创建一个根节点
        ThreadedNode root = new ThreadedNode(1);
        //把根节点赋给树
        binaryTree.setRoot(root);
        //创建左,右节点
        ThreadedNode rootLeft = new ThreadedNode(2);
        ThreadedNode rootRight = new ThreadedNode(3);
        //把新建的节点设置为根节点的子节点
        root.setLeftNode(rootLeft);
        root.setRightNode(rootRight);
        //为第二层的左节点创建两个子节点
        rootLeft.setLeftNode(new ThreadedNode(4));
        ThreadedNode fiveNode = new ThreadedNode(5);
        rootLeft.setRightNode(fiveNode);
        //为第二层的右节点创建两个子节点
        rootRight.setLeftNode(new ThreadedNode(6));
        rootRight.setRightNode(new ThreadedNode(7));

        //中序遍历
        binaryTree.middleShow(); //4 2 5 1 6 3 7
        System.out.println();
        //中序线索化二叉树
        binaryTree.threadNodes();//        //获取5的后继节点//        ThreadedNode afterFive = fiveNode.rightNode;//        System.out.println(afterFive.value); //1
        binaryTree.threadIterate(); //4 2 5 1 6 3 7
    }}

7.7 二叉排序树(Binary Sort Tree)

无序序列
Detailed graphic explanation of Java data structures and algorithms二叉排序树图解
Detailed graphic explanation of Java data structures and algorithms

概述:二叉排序树(Binary Sort Tree)也叫二叉查找树或者是一颗空树,对于二叉树中的任何一个非叶子节点,要求左子节点比当前节点值小,右子节点比当前节点值大

特点

  • 查找性能与插入删除性能都适中还不错
  • 中序遍历的结果刚好是从大到小

创建二叉排序树原理:其实就是不断地插入节点,然后进行比较。

删除节点

  • 删除叶子节点,只需要找到父节点,将父节点与他的连接断开即可
  • 删除有一个子节点的就需要将他的子节点换到他现在的位置
  • 删除有两个子节点的节点,需要使用他的前驱节点或者后继节点进行替换,就是左子树最右下方的数(最大的那个)或右子树最左边的树(最小的数);即离节点值最接近的值;(还要注解要去判断这个值有没有右节点,有就要将右节点移上来)

代码实现

  • 树类
public class BinarySortTree {
    Node root;

    //添加节点
    public void add(Node node) {
        //如果是一颗空树
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }

    //中序遍历
    public void middleShow() {
        if (root != null) {
            root.middleShow(root);
        }
    }

    //查找节点
    public Node search(int value) {
        if (root == null) {
            return null;
        }
        return root.search(value);
    }

    //查找父节点
    public Node searchParent(int value) {
        if (root == null) {
            return null;
        }
        return root.searchParent(value);
    }

    //删除节点
    public void delete(int value) {
        if (root == null) {
            return;
        } else {
            //找到这个节点
            Node target = search(value);
            //如果没有这个节点
            if (target == null) {
                return;
            }
            //找到他的父节点
            Node parent = searchParent(value);
            //要删除的节点是叶子节点
            if (target.left == null && target.left == null) {
                //要删除的节点是父节点的左子节点
                if (parent.left.value == value) {
                    parent.left = null;
                }
                //要删除的节点是父节点的右子节点
                else {
                    parent.right = null;
                }
            }
            //要删除的节点有两个子节点的情况
            else if (target.left != null && target.right != null) {
                //删除右子树中值最小的节点,并且获取到值
                int min = deletMin(target.right);
                //替换目标节点中的值
                target.value = min;
            }
            //要删除的节点有一个左子节点或右子节点
            else {
                //有左子节点
                if (target.left != null) {
                    //要删除的节点是父节点的左子节点
                    if (parent.left.value == value) {
                        parent.left = target.left;
                    }
                    //要删除的节点是父节点的右子节点
                    else {
                        parent.right = target.left;
                    }

                }
                //有右子节点
                else {
                    //要删除的节点是父节点的左子节点
                    if (parent.left.value == value) {
                        parent.left = target.right;
                    }
                    //要删除的节点是父节点的右子节点
                    else {
                        parent.right = target.right;
                    }
                }
            }
        }
    }

    //删除一棵树中最小的节点
    private int deletMin(Node node) {
        Node target = node;
        //递归向左找最小值
        while (target.left != null) {
            target = target.left;
        }
        //删除最小的节点
        delete(target.value);
        return target.value;
    }}
  • 节点类
public class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    //向子树中添加节点
    public void add(Node node) {
        if (node == null) {
            return;
        }
        /*判断传入的节点的值比当前紫薯的根节点的值大还是小*/
        //添加的节点比当前节点更小(传给左节点)
        if (node.value  value && this.left != null) {
                return this.left.searchParent(value);
            } else if (this.value 
  • 测试类
public class Demo {
    public static void main(String[] args) {
        int[] arr = {8, 3, 10, 1, 6, 14, 4, 7, 13};
        //创建一颗二叉排序树
        BinarySortTree bst = new BinarySortTree();
        //循环添加/*        for(int i=0;i<h2>7.8 平衡二叉树( Balanced Binary Tree)</h2><h3>为什么使用平衡二叉树?</h3><p>平衡二叉树(Balanced Binary Tree)又被称为AVL树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在<code>O(logN)</code>。但是频繁旋转会使插入和删除牺牲掉<code>O(logN)</code>左右的时间,不过相对二叉查找树来说,时间上稳定了很多。</p><p>二叉排序树插入 {1,2,3,4,5,6} 这种数据结果如下图所示:<br><img src="https://img.php.cn/upload/article/000/000/067/3ecfcc25f4c8b7a56d063b2b27bace0c-23.png" alt="Detailed graphic explanation of Java data structures and algorithms"><br> 平衡二叉树插入 {1,2,3,4,5,6} 这种数据结果如下图所示:<br><img src="https://img.php.cn/upload/article/000/000/067/3ecfcc25f4c8b7a56d063b2b27bace0c-24.png" alt="Detailed graphic explanation of Java data structures and algorithms"></p><h3>如何判断平衡二叉树?</h3>
  • 1、是二叉排序树
  • 2、任何一个节点的左子树或者右子树都是平衡二叉树(左右高度差小于等于 1)

(1)下图不是平衡二叉树,因为它不是二叉排序树违反第 1 条件
Detailed graphic explanation of Java data structures and algorithms
(2)下图不是平衡二叉树,因为有节点子树高度差大于 1 违法第 2 条件(5的左子树为0,右子树为2)
Detailed graphic explanation of Java data structures and algorithms

(3)下图是平衡二叉树,因为符合 1、2 条件
Detailed graphic explanation of Java data structures and algorithms

相关概念

平衡因子 BF

  • Definition: The height difference between the left subtree and the right subtree
  • Calculation: The height of the left subtree - the value of the height of the right subtree
  • Alias: Abbreviated as BF (Balance Factor)
  • Generally speaking, if the absolute value of BF is greater than 1, the balanced tree binary tree will be unbalanced and needs to be rotated to correct

Minimum unbalanced subtree

  • #The node closest to the inserted node and with an absolute value of BF greater than 1 is the subtree of the root node.

  • Rotation correction only needs to correct the minimum unbalanced subtree

  • An example is shown in the figure below:
    Detailed graphic explanation of Java data structures and algorithms

Rotation method

2 rotation methods:

Left rotation:

  • The old root node is The left subtree of the new root node
  • The left subtree of the new root node (if it exists) is the right subtree of the old root node

Right rotation:

  • The old root node is the right subtree of the new root node
  • The right subtree of the new root node (if it exists) is the left subtree of the old root node

4 types of rotation correction:

  • Left-left type: insert the left subtree of the left child, right rotation
  • Right-right type: insert the right subtree of the right child , Left-handed type
  • Left-left type: Insert the right subtree of the left child, first rotate left, then rotate right
  • Right-left type: Insert the left subtree of the right child, first rotate right, then rotate left
    Detailed graphic explanation of Java data structures and algorithms

Left-left type:

When the third node (1) is inserted, BF(3) = 2, BF(2) = 1, right-handed, root node rotates clockwise
Detailed graphic explanation of Java data structures and algorithms
Right-right type:

The third node (3) When inserting, BF(1)=-2 BF(2)=-1, RR type unbalanced, left-handed, root node rotates counterclockwise
Detailed graphic explanation of Java data structures and algorithms
left-right type :

When the third node (3) is inserted, BF(3)=2 BF(1)=-1 LR type imbalance, first left-handed and then right-handed
Detailed graphic explanation of Java data structures and algorithms
Detailed graphic explanation of Java data structures and algorithms
Right-left type:

When the third node (1) is inserted, BF(1)=-2 BF(3)=1 RL type imbalance, first right-hand rotation and then left-hand rotation

Detailed graphic explanation of Java data structures and algorithms
Detailed graphic explanation of Java data structures and algorithms

Example

(1), Insert 3, 2, and 1 in sequence. When inserting the third point 1, BF(3)=2 BF(2)=1, LL type imbalance, for the minimum unbalanced tree {3,2,1} Perform right rotation

  • The old root node (node ​​3) is the right subtree of the new root node (node ​​2)
  • The right subtree of the new root node (node ​​2) (here No right subtree) is the left subtree of the old root node
    Detailed graphic explanation of Java data structures and algorithms

(2) Insert 4 and 5 in sequence. When inserting 5 pointsBF(3) = - 2 BF(4)=-1, RR type imbalance, perform left rotation on the minimum unbalanced tree {3,4,5}

  • The old root node (node ​​3) is the new root node The left subtree of (node ​​4)
  • The left subtree of the new root node (node ​​4) (there is no left subtree here) is the right subtree of the old root node
    Detailed graphic explanation of Java data structures and algorithms

(3) When inserting 4 and 5 into 5 points, BF(2)=-2 BF(4)=-1, RR type imbalance is the minimum imbalance tree {1,2 ,4} Perform left rotation

  • The old root node (node ​​2) is the left subtree of the new root node (node ​​4)
    Detailed graphic explanation of Java data structures and algorithms
  • The new root node (node 4) The left subtree (node ​​3) is the right subtree of the old root node
    Detailed graphic explanation of Java data structures and algorithms

(4) When inserting node 7BF(5)=- 2, BF(6)=-1, RR type imbalance, left-handed rotation of the minimum imbalance tree

  • 旧根节点(节点 5)为新根节点(节点 6)的左子树
  • 新根节点的左子树(这里没有)为旧根节点的右子树
    Detailed graphic explanation of Java data structures and algorithms

(5)依次插入 10 ,9 。插入 9 点的时候 BF(10) = 1,BF(7) = -2,RL 型失衡,对先右旋再左旋,右子树先右旋

  • 旧根节点(节点 10)为新根节点(节点 9)的右子树
  • 新根节点(节点 9)的右子树(这里没有右子树)为旧根节点的左子树
    Detailed graphic explanation of Java data structures and algorithms
    最小不平衡子树再左旋:
  • 旧根节点(节点 7)为新根节点(节点 9)的左子树
  • 新根节点(节点 9)的左子树(这里没有左子树)为旧根节点的右子树
    Detailed graphic explanation of Java data structures and algorithms

代码实现

Detailed graphic explanation of Java data structures and algorithms

  • 节点类
public class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    //获取当前节点高度
    public int height() {
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    //获取左子树高度
    public int leftHeight() {
        if (left == null) {
            return 0;
        }
        return left.height();
    }

    //获取右子树高度
    public int rightHeight() {
        if (right == null) {
            return 0;
        }
        return right.height();
    }


    //向子树中添加节点
    public void add(Node node) {
        if (node == null) {
            return;
        }
        /*判断传入的节点的值比当前紫薯的根节点的值大还是小*/
        //添加的节点比当前节点更小(传给左节点)
        if (node.value = 2) {
            //双旋转,当左子树左边高度小于左子树右边高度时
            if (left != null && left.leftHeight()  value && this.left != null) {
                return this.left.searchParent(value);
            } else if (this.value 
  • 测试类
public class Demo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6};
        //创建一颗二叉排序树
        BinarySortTree bst = new BinarySortTree();
        //循环添加
        for (int i : arr) {
            bst.add(new Node(i));
        }
        //查看高度
        System.out.println(bst.root.height()); //3
        //查看节点值
        System.out.println(bst.root.value); //根节点为4
        System.out.println(bst.root.left.value); //左子节点为2
        System.out.println(bst.root.right.value); //右子节点为5
    }}

第8章 赫夫曼树

8.1 赫夫曼树概述

HuffmanTree因为翻译不同所以有其他的名字:赫夫曼树、霍夫曼树、哈夫曼树

赫夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明赫夫曼树的WPL是最小的。

8.2 赫夫曼树定义

路径: 路径是指从一个节点到另一个节点的分支序列。

路径长度: 指从一个节点到另一个结点所经过的分支数目。 如下图:从根节点到a的分支数目为2
Detailed graphic explanation of Java data structures and algorithms

树的路径长度: 树中所有结点的路径长度之和为树的路径长度PL。 如下图:PL为10
Detailed graphic explanation of Java data structures and algorithms

节点的权: 给树的每个结点赋予一个具有某种实际意义的实数,我们称该实数为这个结点的权。如下图:7、5、2、4
Detailed graphic explanation of Java data structures and algorithms
带权路径长度: 从树根到某一结点的路径长度与该节点的权的乘积,叫做该结点的带权路径长度。如下图:A的带权路径长度为2*7=14
Detailed graphic explanation of Java data structures and algorithms

树的带权路径长度(WPL): 树的带权路径长度为树中所有叶子节点的带权路径长度之和

最优二叉树:权值最大的节点离跟节点越近的二叉树,所得WPL值最小,就是最优二叉树。如下图:(b)
Detailed graphic explanation of Java data structures and algorithms

  • (a)WPL=9*2+4*2+5*2+2*2=40
  • (b)WPL=9*1+5*2+4*3+2*3=37
  • (c) WPL=4*1+2*2+5*3+9*3=50

8.3 构造赫夫曼树步骤

对于数组{5,29,7,8,14,23,3,11},我们把它构造成赫夫曼树

第一步:使用数组中所有元素创建若干个二叉树,这些值作为节点的权值(只有一个节点)。
Detailed graphic explanation of Java data structures and algorithms
第二步:将这些节点按照权值的大小进行排序。
Detailed graphic explanation of Java data structures and algorithms

第三步:取出权值最小的两个节点,并创建一个新的节点作为这两个节点的父节点,这个父节点的权值为两个子节点的权值之和。将这两个节点分别赋给父节点的左右节点
Detailed graphic explanation of Java data structures and algorithms
第四步:删除这两个节点,将父节点添加进集合里
Detailed graphic explanation of Java data structures and algorithms

第五步:重复第二步到第四步,直到集合中只剩一个元素,结束循环
Detailed graphic explanation of Java data structures and algorithms

8.4 代码实现

  • 节点类
//接口实现排序功能public class Node implements Comparable<node> {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(Node o) {
        return -(this.value - o.value); //集合倒叙,从大到小
    }

    @Override
    public String toString() {
        return "Node value=" + value ;
    }}</node>
  • 测试类
import java.util.ArrayList;import java.util.Collections;import java.util.List;public class Demo {
    public static void main(String[] args) {
        int[] arr = {5, 29, 7, 8, 14, 23, 3, 11};
        Node node = createHuffmanTree(arr);
        System.out.println(node); //Node value=100
    }

    //创建赫夫曼树
    public static Node createHuffmanTree(int[] arr) {
        //使用数组中所有元素创建若干个二叉树(只有一个节点)
        List<node> nodes = new ArrayList();
        for (int value : arr) {
            nodes.add(new Node(value));
        }

        //循环处理
        while (nodes.size() > 1) {
            //排序
            Collections.sort(nodes);
            //取出最小的两个二叉树(集合为倒叙,从大到小)
            Node left = nodes.get(nodes.size() - 1); //权值最小
            Node right = nodes.get(nodes.size() - 2); //权值次小
            //创建一个新的二叉树
            Node parent = new Node(left.value + right.value);
            //删除原来的两个节点
            nodes.remove(left);
            nodes.remove(right);
            //新的二叉树放入原来的二叉树集合中
            nodes.add(parent);
            //打印结果
            System.out.println(nodes);
        }
        return nodes.get(0);
    }}</node>
  • 循环次数结果
[Node value=29, Node value=23, Node value=14, Node value=11, Node value=8, Node value=7, Node value=8][Node value=29, Node value=23, Node value=14, Node value=11, Node value=8, Node value=15][Node value=29, Node value=23, Node value=15, Node value=14, Node value=19][Node value=29, Node value=23, Node value=19, Node value=29][Node value=29, Node value=29, Node value=42][Node value=42, Node value=58][Node value=100]Node value=100Process finished with exit code 0

第9章 多路查找树(2-3树、2-3-4树、B树、B+树)

9.1 为什么使用多路查找树

二叉树存在的问题

二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿), 就存在如下问题:

  • 问题1:在构建二叉树时,需要多次进行I/O操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响.

  • 问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度.
    Detailed graphic explanation of Java data structures and algorithms

解决上述问题 —> 多叉树

多路查找树

  • 1、在二叉树中,每个节点有数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树(multiway tree)
  • 2、后面我们讲解的"2-3树","2-3-4树"就是多叉树,多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化。
  • 3、举例说明(下面2-3树就是一颗多叉树)
    Detailed graphic explanation of Java data structures and algorithms

9.2 2-3树

2-3树定义

  • 所有叶子节点都要在同一层
  • 二节点要么有两个子节点,要么没有节点
  • 三节点要么没有节点,要么有三个子节点
  • 不能出现节点不满的情况
    Detailed graphic explanation of Java data structures and algorithms

2-3树插入的操作

插入原理

对于2-3树的插入来说,与平衡二叉树相同,插入操作一定是发生在叶子节点上,并且节点的插入和删除都有可能导致不平衡的情况发生,在插入和删除节点时也是需要动态维持平衡的,但维持平衡的策略和AVL树是不一样的。

AVL树向下添加节点之后通过旋转来恢复平衡,而2-3树是通过节点向上分裂来维持平衡的,也就是说2-3树插入元素的过程中层级是向上增加的,因此不会导致叶子节点不在同一层级的现象发生,也就不需要旋转了。

三种插入情况

1)对于空树,插入一个2节点即可;

2)插入节点到一个2节点的叶子上。由于本身就只有一个元素,所以只需要将其升级为3节点即可(如:插入3)。

Detailed graphic explanation of Java data structures and algorithms
3)插入节点到一个3节点的叶子上。因为3节点本身最大容量,因此需要拆分,且将树中两元素或者插入元素的三者中选择其一向上移动一层。

分为三种情况:

  • 升级父节点(插入5)
    Detailed graphic explanation of Java data structures and algorithms
  • 升级根节点(插入11)
    Detailed graphic explanation of Java data structures and algorithms
  • 增加树高度(插入2,从下往上拆)
    Detailed graphic explanation of Java data structures and algorithms

2-3树删除的操作

删除原理:2-3树的删除也分为三种情况,与插入相反。

三种删除情况

1) The deleted element is located on a leaf node of 3 nodes. Deleting it directly will not affect the tree structure (such as deleting 9)
Detailed graphic explanation of Java data structures and algorithms
2) The deleted element is located on a 2 node , delete it directly and destroy the tree structure
Detailed graphic explanation of Java data structures and algorithms

It is divided into four situations:

  • The parents of this node are also 2 nodes, and have a 3-node Right child (eg: delete 1)
    Detailed graphic explanation of Java data structures and algorithms

  • The parent of this node is node 2, and its right child is also node 2 (eg: delete 4)
    Detailed graphic explanation of Java data structures and algorithms

  • The parents of this node are 3 nodes (for example: delete 10)
    Detailed graphic explanation of Java data structures and algorithms

  • The current tree is a full tree Binary tree, reduce the tree height (for example: delete 8)
    Detailed graphic explanation of Java data structures and algorithms

3) The deleted element is located at a non-leaf branch node. At this time, the predecessor or subsequent elements of this element are obtained by traversing the tree in order, and the filling

has two situations:

  • The branch node is 2 nodes (for example: delete 4)
    Detailed graphic explanation of Java data structures and algorithms
  • The branch node is 3 nodes (for example: delete 12)
    Detailed graphic explanation of Java data structures and algorithms
##9.3 2-3-4 tree

2 -3-4 tree is an extension of 2-3 tree, including the use of 4 nodes. A 4 node contains three elements, small, medium and large, and four children (or no children)

2-3-4 Tree insertion operation

1) If the node to be inserted is not 4 nodes, just insert it directly

2) If the node to be inserted is 4 nodes, temporarily insert the new node first Go in and become 5 nodes, and then split and merge the 5 nodes upward. The 5 nodes are split into two 2 nodes (the smallest element of the 5 node, the second element of the 5 node), and one 3 node (the two elements after the 5 node). ), then merge the second 2 node after the split upward into the parent node, and then use the parent node as the current node after the inserted element, repeat steps (1) and (2) until the requirements of the 2-3-4 tree are satisfied Define properties


Detailed graphic explanation of Java data structures and algorithms
Detailed graphic explanation of Java data structures and algorithms
Detailed graphic explanation of Java data structures and algorithms

2-3-4 tree deletion operation

The deletion order is 1,6,3, 4,5,2,9


Detailed graphic explanation of Java data structures and algorithms

9.4 B-tree

B-tree (BTree) is a balanced multi-path search tree, 2-3 tree and 2- 3-4 trees are all special cases of B-trees.

We call the child tree with the largest node the order of the B-tree. Therefore, the 2-3 tree is a 3-order B-tree, and the 2-3-4 tree is a 4-order B-tree


Detailed graphic explanation of Java data structures and algorithms

As shown below, for example, to find 7, first read the three elements of the root node 3, 5, and 8 from the external memory. It is found that 7 is not there, but between 5 and 8, so it is searched through A2. Read nodes 2, 6, and 7 from the external memory and find the end.


Detailed graphic explanation of Java data structures and algorithms

The data structure of the B-tree is prepared for data interaction between internal and external memory. When the data to be processed is large, it cannot be loaded into memory all at once. At this time, the B-tree is adjusted so that the order of the B-tree matches the page size stored in the hard disk. For example, a B-tree has an order of 1001 (that is, 1 node contains 1000 keywords) and a height of 2 (starting from 0). It can store more than 1 billion keywords (1001x1001x1000 1001x1000 1000). As long as the root node If it is persisted in memory, then searching for a certain keyword in this tree requires at most two hard disk reads.

For an m-order B-tree with n keywords, Calculation of worst-case search times
There must be at least 1 node in the first level and at least 2 nodes in the second level. Since every node except the root node Each branch node has at least ⌈m/2⌉ subtrees, then the third level has at least 2x⌈m/2⌉ nodes. . . In this way, the k 1th layer has at least 2x(⌈m/2⌉)^(k-1). In fact, the nodes in the k 1th layer are leaf nodes. If the m-order B-tree has n keywords, then when you find the leaf node, it actually means that the unsuccessful node is n 1, so
n 1>=2x(⌈m/2⌉)^(k -1), that is,
Detailed graphic explanation of Java data structures and algorithms

When searching on a B-tree containing n keywords, the number of nodes involved in the path from the root node to the keyword node is not too manyDetailed graphic explanation of Java data structures and algorithms

9.5 B-tree

B-tree can be said to be an upgraded version of B-tree. Compared with B-tree, B-tree makes full use of the node space and makes the query speed more stable. The speed is completely close to binary search. Most file systems and data use B-trees to implement index structures.

We need to traverse the B-tree in the figure below. Assume that each node belongs to a different page on the hard disk. We traverse all the elements in order, page 2-page 1-page 3-page 1-page 4. - Page 1 - Page 5, page 1 has been traversed 3 times, and every time we traverse a node, we will traverse the elements in the node once.
Detailed graphic explanation of Java data structures and algorithms

B The tree is the file system. A deformed tree of the B-tree. In the B-tree, each element appears only once in the tree. In the B-tree, the elements that appear in the branch node will be regarded as their in-order position in the branch node. Listed again in successors (leaf nodes). In addition, each leaf node will save a pointer to the following leaf node.
The picture below is a B-tree. Gray keywords appear at the root node and are listed again in the leaf nodes.
Detailed graphic explanation of Java data structures and algorithms

A B-tree of order m and order m The difference between the B-tree is :

  • There are n sub-trees whose non-leaf nodes contain n keywords (n-1 keywords in the B-tree), but each Keywords do not save data, but are only used to save indexes of the same keywords in leaf nodes. All data is stored in leaf nodes. (Here, for the relationship between m subtrees and n keywords of non-leaf nodes, the index structure of mysql seems to be m=n 1, not the above m=n)
  • All non-leaf nodes Node elements all exist in child nodes at the same time, and it is the largest (or smallest) element among child node elements.
  • All leaf nodes contain information about all keywords and pointer data to the specific disk records pointed to by these keywords, and each leaf node has a pointer to the next adjacent leaf node. Forming a linked list

9.6 Summary

  • The non-leaf nodes of the B-tree will store keywords and their corresponding data addresses, and the non-leaf nodes of the B-tree only store Keyword index does not store specific data addresses, so a node of the B-tree can store more index elements than the B-tree, and the more keywords need to be searched when read into the memory at one time, the height of the B-tree Smaller, the relative number of IO reads and writes is reduced.

  • The query efficiency of the B-tree is not stable. In the best case, it only queries once (the root node). In the worst case, the leaf nodes are found. However, the B-tree does not The data address is stored, but only the index of the key in the leaf node. All queries must find leaf nodes to be considered hits, and the query efficiency is relatively stable. This is very helpful for optimizing SQL statements.

  • All leaf nodes of the B tree form an ordered linked list, and the entire tree can be traversed only by traversing the leaf nodes. It is convenient for range query of data, but B-tree does not support such operation or is too inefficient.

  • Most of the index tables in modern databases and file systems are implemented using B-trees, but not all

Chapter 10 Figure Structure

10.1 The basic concept of graph

Graph (Graph) is a complex nonlinear structure. In the graph structure, each element can have zero or multiple predecessors, and can have zero or more successors, that is, the relationship between elements is arbitrary.

Commonly used terms:

##VertexSomeone in the graph NodesEdgesConnections between verticesAdjacent verticesare connected by the same Vertices connected by edgesDegreeNumber of adjacent vertices of a vertexSimple pathA route from one vertex to another without passing through the vertex repeatedlyLoopThe starting point and the end point are both the same vertexUndirected graphAll edges in the graph have no directionDirected graphAll edges in the graph The edges have directionsUnweighted graphThe edges in the graph have no weight valueWeighted graphThe edges in the graph have a certain weight value

图的结构很简单,就是由顶点 V 集和边 E 集构成,因此图可以表示成 G = (V,E)

无向图

若顶点 Vi 到 Vj 之间的边没有方向,则称这条边为无向边 (Edge) ,用无序偶对 (Vi,Vj) 来表示。如果图中任意两个顶点之间的边都是无向边,则称该图为无向图 (Undirected graphs)

如:下图就是一个无向图,由于是无方向的,连接顶点 A 与 D 的边,可以表示无序队列(A,D),也可以写成 (D,A),但不能重复。顶点集合 V = {A,B,C,D};边集合 E = {(A,B),(A,D),(A,C)(B,C),(C,D),}

Detailed graphic explanation of Java data structures and algorithms

有向图

用有序偶来表示,Vi 称为弧尾 (Tail) , Vj称为弧头 (Head)。 如果图中任意两个顶点之间的边都是有向边,则称该图为有向图 (Directed grahs)

如:下图就是一个有向图。连接顶点 A 到 D 的有向边就是弧,A是弧尾,D 是弧头, 表示弧, 注意不能写成。其中顶点集合 V = { A,B,C,D}; 弧集合 E = {<a>,<b>,<b>,<c>}</c></b></b></a>

Detailed graphic explanation of Java data structures and algorithms
注意:无向边用小括号 “()” 表示,而有向边则是用尖括号""表示

有权图

有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权 (Weight) 。这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网 (Network)。

如下图
Detailed graphic explanation of Java data structures and algorithms

10.2 图的存储结构及实现

图结构的常见的两个存储方式: 邻接矩阵 、邻接表

邻接矩阵

Detailed graphic explanation of Java data structures and algorithms图中的 0 表示该顶点无法通向另一个顶点,相反 1 就表示该顶点能通向另一个顶点

先来看第一行,该行对应的是顶点A,那我们就拿顶点A与其它点一一对应,发现顶点A除了不能通向顶点D和自身,可以通向其它任何一个的顶点

因为该图为无向图,因此顶点A如果能通向另一个顶点,那么这个顶点也一定能通向顶点A,所以这个顶点对应顶点A的也应该是 1

虽然我们确实用邻接矩阵表示了图结构,但是它有一个致命的缺点,那就是矩阵中存在着大量的 0,这在程序中会占据大量的内存。此时我们思考一下,0 就是表示没有,没有为什么还要写,所以我们来看一下第二种表示图结构的方法,它就很好的解决了邻接矩阵的缺陷

代码实现

  • 顶点类
public class Vertex {

    private String value;

    public Vertex(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }}
  • 图类
public class Graph {

    private Vertex[] vertex; //顶点数组
    private int currentSize; //默认顶点位置
    public int[][] adjMat; //邻接表

    public Graph(int size) {
        vertex = new Vertex[size];
        adjMat = new int[size][size];
    }

    //向图中加入顶点
    public void addVertex(Vertex v) {
        vertex[currentSize++] = v;
    }

    //添加边
    public void addEdge(String v1, String v2) {
        //找出两个点的下标
        int index1 = 0;
        for (int i = 0; i 
  • 测试类
public class Demo {
    public static void main(String[] args) {
        Vertex v1 = new Vertex("A");
        Vertex v2 = new Vertex("B");
        Vertex v3 = new Vertex("C");
        Vertex v4 = new Vertex("D");
        Vertex v5 = new Vertex("E");
        Graph g = new Graph(5);
        g.addVertex(v1);
        g.addVertex(v2);
        g.addVertex(v3);
        g.addVertex(v4);
        g.addVertex(v5);

        //增加边
        g.addEdge("A", "B");
        g.addEdge("A", "C");
        g.addEdge("A", "E");
        g.addEdge("C", "E");
        g.addEdge("C", "D");

        for (int[] a : g.adjMat) {
            System.out.println(Arrays.toString(a));
        }
    }}
  • 结果值
[0, 1, 1, 0, 1][1, 0, 0, 0, 0][1, 0, 0, 1, 1][0, 0, 1, 0, 0][1, 0, 1, 0, 0]

邻接表

邻接表 是由每个顶点以及它的相邻顶点组成的
Detailed graphic explanation of Java data structures and algorithms

如上图:图中最左侧红色的表示各个顶点,它们对应的那一行存储着与它相关联的顶点

  • 顶点A 与 顶点B 、顶点C 、顶点E 相关联
  • 顶点B 与 顶点A 相关联
  • 顶点C 与 顶点A 、顶点D 、顶点E 相关联
  • 顶点D 与 顶点C 相关联
  • 顶点E 与 顶点A 、顶点C 相关联

10.3 图的遍历方式及实现

从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历

在图结构中,存在着两种遍历搜索的方式,分别是 广度优先搜索深度优先搜索

广度优先搜索

广度优先遍历(BFS):类似于图的层次遍历,它的基本思想是:首先访问起始顶点v,然后选取v的所有邻接点进行访问,再依次对v的邻接点相邻接的所有点进行访问,以此类推,直到所有顶点都被访问过为止

BFS和树的层次遍历一样,采取队列实现,这里添加一个标记数组,用来标记遍历过的顶点

Detailed graphic explanation of Java data structures and algorithms

执行步骤

  • 1、先把 A 压入队列,然后做出队操作,A 出队
  • 2、把 A 直接相关的顶点 ,B、F 做入队操作
  • 3、B 做出队操作,B 相关的点 C、I、G 做入队操作
  • 4、F 做出队操作,F 相关的点 E 做入队操作
  • 5、C 做出队操作,C 相关的点 D 做入队操作
  • 6、I 做出队操作(I 相关的点B、C、D 都已经做过入队操作了,不能重复入队)
  • 7、G 做出队操作,G 相关的点 H 做入队操作
  • 8、E 做出队操作…
  • 9、D 做出队操作…
  • 10、H 做出队操作,没有元素了

代码实现

深度优先搜索

深度优先遍历(DFS):从一个顶点开始,沿着一条路径一直搜索,直到到达该路径的最后一个结点,然后回退到之前经过但未搜索过的路径继续搜索,直到所有路径和结点都被搜索完毕

DFS与二叉树的先序遍历类似,可以采用递归或者栈的方式实现
Detailed graphic explanation of Java data structures and algorithms

执行步骤

  • 1、从 1 出发,路径为:1 -> 2 -> 3 -> 6 -> 9 -> 8 -> 5 -> 4
  • 2、当搜索到 4 时,相邻没有发现未被访问的点,此时我们要往后倒退,找寻别的没搜索过的路径
  • 3、退回到 5 ,相邻没有发现未被访问的点,继续后退
  • 4、退回到 8 ,相邻发现未被访问的点 7,路径为:8 -> 7
  • 5、当搜索到 7 ,相邻没有发现未被访问的点,,此时我们要往后倒退…
  • 6、退回路径7 -> 8 -> 9 -> 6 -> 3 -> 2 -> 1,流程结束

代码实现

  • 栈类
public class MyStack {

    //栈的底层使用数组来存储数据
    //private int[] elements;
    int[] elements; //测试时使用

    public MyStack() {
        elements = new int[0];
    }

    //添加元素
    public void push(int element) {
        //创建一个新的数组
        int[] newArr = new int[elements.length + 1];
        //把原数组中的元素复制到新数组中
        for (int i = 0; i 
  • 顶点类
public class Vertex {

    private String value;
    public boolean visited; //访问状态

    public Vertex(String value) {
        super();
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return value;
    }}
  • 图类
import mystack.MyStack;public class Graph {

    private Vertex[] vertex; //顶点数组
    private int currentSize; //默认顶点位置
    public int[][] adjMat; //邻接表
    private MyStack stack = new MyStack(); //栈
    private int currentIndex; //当前遍历的下标

    public Graph(int size) {
        vertex = new Vertex[size];
        adjMat = new int[size][size];
    }

    //向图中加入顶点
    public void addVertex(Vertex v) {
        vertex[currentSize++] = v;
    }

    //添加边
    public void addEdge(String v1, String v2) {
        //找出两个点的下标
        int index1 = 0;
        for (int i = 0; i 
  • 测试类
import java.util.Arrays;public class Demo {
    public static void main(String[] args) {
        Vertex v1 = new Vertex("A");
        Vertex v2 = new Vertex("B");
        Vertex v3 = new Vertex("C");
        Vertex v4 = new Vertex("D");
        Vertex v5 = new Vertex("E");
        Graph g = new Graph(5);
        g.addVertex(v1);
        g.addVertex(v2);
        g.addVertex(v3);
        g.addVertex(v4);
        g.addVertex(v5);

        //增加边
        g.addEdge("A", "B");
        g.addEdge("A", "C");
        g.addEdge("A", "E");
        g.addEdge("C", "E");
        g.addEdge("C", "D");

        for (int[] a : g.adjMat) {
            System.out.println(Arrays.toString(a));
        }

        //深度优先遍历
        g.dfs();//        A//        B//        C//        E//        D
    }}

第11章 冒泡排序(含改进版)

11.1 冒泡排序概念

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

运行流程

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

动图实现
Detailed graphic explanation of Java data structures and algorithms
静图详解

交换过程图示(第一次):
Detailed graphic explanation of Java data structures and algorithms那么我们需要进行n-1次冒泡过程,每次对应的比较次数如下图所示:
Detailed graphic explanation of Java data structures and algorithms

11.2 代码实现

import java.util.Arrays;public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        bubbleSort(arr);//        [4, 5, 3, 2, 1, 6]//        [4, 3, 2, 1, 5, 6]//        [3, 2, 1, 4, 5, 6]//        [2, 1, 3, 4, 5, 6]//        [1, 2, 3, 4, 5, 6]
    }

    //冒泡排序,共需要比较length-1轮
    public static void bubbleSort(int[] arr) {
        //控制一共比较多少轮
        for (int i = 0; i  arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            //打印每次排序后的结果
            System.out.println(Arrays.toString(arr)); 
        }
    }}

11.3 时间复杂度

  • 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
  • 最坏时间复杂度:O(n^2)
  • 稳定性:稳定

排序分析:待排数组中一共有6个数,第一轮排序时进行了5次比较,第二轮排序时进行了4比较,依次类推,最后一轮进行了1次比较。

数组元素总数为N时,则一共需要的比较次数为:(N-1)+ (N-2)+ (N-3)+ ...1=N*(N-1)/2

算法约做了N^2/2次比较。因为只有在前面的元素比后面的元素大时才交换数据,所以交换的次数少于比较的次数。如果数据是随机的,大概有一半数据需要交换,则交换的次数为N^2/4(不过在最坏情况下,即初始数据逆序时,每次比较都需要交换)。

交换和比较的操作次数都与 N^2 成正比,由于在大O表示法中,常数忽略不计,冒泡排序的时间复杂度为O(N^2)

O(N2)的时间复杂度是一个比较糟糕的结果,尤其在数据量很大的情况下。所以冒泡排序通常不会用于实际应用。

11.4 代码改进

传统的冒泡算法每次排序只确定了最大值,我们可以在每次循环之中进行正反两次冒泡,分别找到最大值和最小值,如此可使排序的轮数减少一半

import java.util.Arrays;public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        bubbleSort(arr);//        [1, 4, 5, 3, 2, 6]//        [1, 2, 4, 3, 5, 6]//        [1, 2, 3, 4, 5, 6]
    }

    //冒泡排序改进
    public static void bubbleSort(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        while (left  arr[i + 1]) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            --right;

            //反向冒泡,确定最小值
            for (int j = right; j > left; --j) {
                //如果前一位大于后一位,交换位置
                if (arr[j] <h2>第12章 选择排序(含改进版)</h2><h2>12.1 选择排序概念</h2><p><strong>选择排序</strong>(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。</p><p>选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种</p><p><strong>动图展示</strong>:</p><p>动图1:<br><img src="https://img.php.cn/upload/article/000/000/067/3b7ad076c398e7fcf7a71a090a7e1fee-89.gif" alt="Detailed graphic explanation of Java data structures and algorithms"><br> 动图2:<br><img src="https://img.php.cn/upload/article/000/000/067/4aef1844623c7df749806b371054cd1f-90.gif" alt="Detailed graphic explanation of Java data structures and algorithms"></p><h2>12.2 代码实现</h2><pre class="brush:php;toolbar:false">import java.util.Arrays;public class seletsort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        selectSort(arr);//        [1, 5, 6, 3, 2, 4]//        [1, 2, 6, 3, 5, 4]//        [1, 2, 3, 6, 5, 4]//        [1, 2, 3, 4, 5, 6]//        [1, 2, 3, 4, 5, 6]//        [1, 2, 3, 4, 5, 6]
    }

    //选择排序
    public static void selectSort(int[] arr) {
        //遍历所有的数
        for (int i = 0; i  arr[j]) {
                    //记录最小的那个数的下标
                    minIndex = j;
                }
            }
            //如果发现了更小的元素,与第一个元素交换位置(第一个不是最小的元素)
            if (i != minIndex) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
            //打印每次排序后的结果
            System.out.println(Arrays.toString(arr));
        }
    }}

12.3 时间复杂度

  • 最优时间复杂度:O(n^2)
  • 最坏时间复杂度:O(n^2)
  • 稳定性:不稳定(考虑升序每次选择最大的情况)

选择排序与冒泡排序一样,需要进行N*(N-1)/2次比较,但是只需要N次交换,当N很大时,交换次数的时间影响力更大,所以选择排序的时间复杂度为O(N^2)

虽然选择排序与冒泡排序在时间复杂度属于同一量级,但是毫无疑问选择排序的效率更高,因为它的交换操作次数更少,而且在交换操作比比较操作的时间级大得多时,选择排序的速度是相当快的。

12.4 代码改进

传统的选择排序每次只确定最小值,根据改进冒泡算法的经验,我们可以对排序算法进行如下改进:每趟排序确定两个最值——最大值与最小值,这样就可以将排序趟数缩减一半

改进后代码如下:

import java.util.Arrays;public class seletsort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        selectSort(arr);//        [1, 5, 4, 3, 2, 6]//        [1, 2, 4, 3, 5, 6]//        [1, 2, 3, 4, 5, 6]

    }

    //选择排序改进
    public static void selectSort(int[] arr) {
        int minIndex; // 存储最小元素的小标
        int maxIndex; // 存储最大元素的小标

        for (int i = 0; i  arr[maxIndex]) {
                    maxIndex = j;
                }
            }

            //如果发现了更小的元素,与第一个元素交换位置(第一个不是最小的元素)
            if (i != minIndex) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;

                // 原来的第一个元素已经与下标为minIndex的元素交换了位置
                // 所以现在arr[minIndex]存放的才是之前第一个元素中的数据
                // 如果之前maxIndex指向的是第一个元素,那么需要将maxIndex重新指向arr[minIndex]
                if (maxIndex == i) {
                    maxIndex = minIndex;
                }
            }

            // 如果发现了更大的元素,与最后一个元素交换位置
            if (arr.length - 1 - i != maxIndex) {
                int temp = arr[arr.length - 1 - i];
                arr[arr.length - 1 - i] = arr[maxIndex];
                arr[maxIndex] = temp;
            }
            System.out.println(Arrays.toString(arr));
        }
    }}

第13章 插入排序(含改进版)

13.1 插入排序概念

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间

动图展示

Detailed graphic explanation of Java data structures and algorithms

13.2 代码实现

import java.util.Arrays;public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        insertSort(arr);//        [4, 5, 6, 3, 2, 1]//        [4, 5, 6, 3, 2, 1]//        [3, 4, 5, 6, 2, 1]//        [2, 3, 4, 5, 6, 1]//        [1, 2, 3, 4, 5, 6]
    }

    //插入排序
    public static void insertSort(int[] arr) {
        //遍历所有的数字,从第二个开始和前一个比较
        for (int i = 1; i = 0 && temp <h2>13.3 时间复杂度</h2>
  • 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
  • 最坏时间复杂度:O(n^2)
  • 稳定性:稳定

在第一趟排序中,插入排序最多比较一次,第二趟最多比较两次,依次类推,最后一趟最多比较N-1次。因此有:1+2+3+...+N-1 = N*N(N-1)/2

因为在每趟排序发现插入点之前,平均来说,只有全体数据项的一半进行比较,我们除以2得到:N*N(N-1)/4

复制的次数大致等于比较的次数,然而,一次复制与一次比较的时间消耗不同,所以相对于随机数据,这个算法比冒泡排序快一倍,比选择排序略快。

与冒泡排序、选择排序一样,插入排序的时间复杂度仍然为O(N^2),这三者被称为简单排序或者基本排序,三者都是稳定的排序算法。

如果待排序数组基本有序时,插入排序的效率会更高

13.4 代码改进

在插入某个元素之前需要先确定该元素在有序数组中的位置,上例的做法是对有序数组中的元素逐个扫描,当数据量比较大的时候,这是一个很耗时间的过程,可以采用二分查找法改进,这种排序也被称为二分插入排序

import java.util.Arrays;public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {4, 5, 6, 3, 2, 1};
        insertSort(arr);//        [4, 5, 6, 3, 2, 1]//        [4, 5, 6, 3, 2, 1]//        [3, 4, 5, 6, 2, 1]//        [2, 3, 4, 5, 6, 1]//        [1, 2, 3, 4, 5, 6]
    }

    //二分插入排序
    public static void insertSort(int[] arr) {
        for (int i = 1; i  left; j--) {
                    arr[j] = arr[j - 1];
                }
                arr[left] = temp;
            }
            System.out.println(Arrays.toString(arr));
        }
    }}

第14章 归并排序

14.1 归并排序概念

归并排序(Merge Sort)是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。

将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

动图展示

  • 动图1

Detailed graphic explanation of Java data structures and algorithms

  • 动图2

Detailed graphic explanation of Java data structures and algorithms

14.2 代码实现

import java.util.Arrays;public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {6, 5, 3, 1, 8, 7, 2, 4};
        mergeSort(arr, 0, arr.length - 1);//        [5, 6, 3, 1, 8, 7, 2, 4]//        [5, 6, 1, 3, 8, 7, 2, 4]//        [1, 3, 5, 6, 8, 7, 2, 4]//        [1, 3, 5, 6, 7, 8, 2, 4]//        [1, 3, 5, 6, 7, 8, 2, 4]//        [1, 3, 5, 6, 2, 4, 7, 8]//        [1, 2, 3, 4, 5, 6, 7, 8]
    }

    //归并排序
    public static void mergeSort(int[] arr, int low, int high) {
        int middle = (high + low) / 2;
        //递归结束
        if (low <h2>14.3 时间复杂度</h2><p>最优时间复杂度:<code>O(nlogn)</code></p><p>最坏时间复杂度:<code>O(nlogn)</code></p><p>稳定性:稳定</p><h2>第15章 快速排序</h2><h2>15.1 快速排序概念</h2><p><strong>快速排序</strong>(Quick Sort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。</p><p><strong>排序步骤</strong>:</p>
  • 1、 从数列中挑出一个元素,称为"基准"(pivot),通常选择第一个元素
  • 2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  • 3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

动图展示

  • 动图1

Detailed graphic explanation of Java data structures and algorithms

  • 动图2:

Detailed graphic explanation of Java data structures and algorithms
静图分析
Detailed graphic explanation of Java data structures and algorithms

15.2 代码实现

import java.util.Arrays;public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {30, 40, 60, 10, 20, 50};
        quickSort(arr, 0, arr.length - 1);//        [20, 10, 30, 60, 40, 50]//        [10, 20, 30, 60, 40, 50]//        [10, 20, 30, 60, 40, 50]//        [10, 20, 30, 50, 40, 60]//        [10, 20, 30, 40, 50, 60]//        [10, 20, 30, 40, 50, 60]
    }

    //快速排序
    public static void quickSort(int[] arr, int start, int end) {
        //递归结束的标记
        if (start = stard) {
                    high--;
                }
                //右边数字比标准数小,使用右边的数替换左边的数
                arr[low] = arr[high];
                //如果左边数字比标准数小
                while (low <h2>15.3 时间复杂度</h2>
  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n^2)
  • 稳定性:不稳定

从一开始快速排序平均需要花费O(n log n)时间的描述并不明显。但是不难观察到的是分区运算,数组的元素都会在每次循环中走访过一次,使用O(n)的时间。在使用结合(concatenation)的版本中,这项运算也是O(n)。

在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。

第16章 希尔排序

16.1 希尔排序概念

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

动图展示

Detailed graphic explanation of Java data structures and algorithms

静图分析
Detailed graphic explanation of Java data structures and algorithms

16.2 代码实现

import java.util.Arrays;public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        shellSort(arr);//        [3, 5, 1, 6, 0, 8, 9, 4, 7, 2]//        [0, 2, 1, 4, 3, 5, 7, 6, 9, 8]//        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }

    //希尔排序
    public static void shellSort(int[] arr) {
        //遍历所有的步长
        for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
            //遍历所有的元素
            for (int i = gap; i = 0; j -= gap) {
                    //如果当前元素大于加上步长后的那个元素
                    if (arr[j] > arr[j + gap]) {
                        int temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
            //打印每次排序后的结果
            System.out.println(Arrays.toString(arr));
        }
    }}

16.3 时间复杂度

  • 最优时间复杂度:根据步长序列的不同而不同
  • 最坏时间复杂度:O(n^2)
  • 稳定性:不稳定

希尔排序不像其他时间复杂度为O(N log2N)的排序算法那么快,但是比选择排序和插入排序这种时间复杂度为O(N2)的排序算法还是要快得多,而且非常容易实现。它在最坏情况下的执行效率和在平均情况下的执行效率相比不会降低多少,而快速排序除非采取特殊措施,否则在最坏情况下的执行效率变得非常差。

迄今为止,还无法从理论上精准地分析希尔排序的效率,有各种各样基于试验的评估,估计它的时间级介于O(N^3/2)与 O(N^7/6)之间。我们可以认为希尔排序的平均时间复杂度为o(n^1.3)

第17章 堆排序

17.1 堆排序概述

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

Detailed graphic explanation of Java data structures and algorithms同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
Detailed graphic explanation of Java data structures and algorithms该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:

  • 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

  • 小顶堆:arr[i]

步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)

1)假设给定无序序列结构如下
Detailed graphic explanation of Java data structures and algorithms
2)此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整
Detailed graphic explanation of Java data structures and algorithms
3)找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换
Detailed graphic explanation of Java data structures and algorithms4)这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
Detailed graphic explanation of Java data structures and algorithms此时,我们就将一个无需序列构造成了一个大顶堆

步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换

1)将堆顶元素9和末尾元素4进行交换,9就不用继续排序了
Detailed graphic explanation of Java data structures and algorithms
2)重新调整结构,使其继续构建大顶堆(9除外)
Detailed graphic explanation of Java data structures and algorithms
3)再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
Detailed graphic explanation of Java data structures and algorithms
步骤三 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
Detailed graphic explanation of Java data structures and algorithms

排序思路

  • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  • 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序

动图展示

Detailed graphic explanation of Java data structures and algorithms

17.2 代码实现

import java.util.Arrays;public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {4, 6, 8, 5, 9};
        heapSort(arr);//        [4, 6, 8, 5, 9]//        [4, 9, 8, 5, 6]//        [4, 9, 8, 5, 6]//        [9, 6, 8, 5, 4]//        [9, 6, 8, 5, 4]//        [9, 6, 8, 5, 4]//        [8, 6, 4, 5, 9]//        [8, 6, 4, 5, 9]//        [6, 5, 4, 8, 9]//        [6, 5, 4, 8, 9]//        [5, 4, 6, 8, 9]//        [5, 4, 6, 8, 9]//        [4, 5, 6, 8, 9]
    }

    //堆排序
    public static void heapSort(int[] arr) {
        //开始位置是最后一个非叶子节点(最后一个节点的父节点)
        int start = (arr.length - 1) / 2;
        //循环调整为大顶堆
        for (int i = start; i >= 0; i--) {
            maxHeap(arr, arr.length, i);
        }
        //先把数组中第0个和堆中最后一个交换位置
        for (int i = arr.length - 1; i > 0; i--) {
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            //再把前面的处理为大顶堆
            maxHeap(arr, i, 0);
        }
    }

    //数组转大顶堆,size:调整多少(从最后一个向前减),index:调整哪一个(最后一个非叶子节点)
    public static void maxHeap(int[] arr, int size, int index) {
        //左子节点
        int leftNode = 2 * index + 1;
        //右子节点
        int rightNode = 2 * index + 2;
        //先设当前为最大节点
        int max = index;
        //和两个子节点分别对比,找出最大的节点
        if (leftNode  arr[max]) {
            max = leftNode;
        }
        if (rightNode  arr[max]) {
            max = rightNode;
        }

        //交换位置
        if (max != index) {
            int temp = arr[index];
            arr[index] = arr[max];
            arr[max] = temp;
            //交换位置后,可能会破坏之前排好的堆,所以之间排好的堆需要重新调整
            maxHeap(arr, size, max);
        }
        //打印每次排序后的结果
        System.out.println(Arrays.toString(arr));
    }}

17.3 时间复杂度

  • 最优时间复杂度:o(nlogn)
  • 最坏时间复杂度:o(nlogn)
  • 稳定性:不稳定

它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。

在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。

在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为log2i+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)

所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。

空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序是一种不稳定的排序方法。

第18章 计数排序

18.1 计数排序概念

计数排序(Counting Sort)不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

排序步骤

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

动图展示

Detailed graphic explanation of Java data structures and algorithms

18.2 代码实现

import java.util.Arrays;public class CountingSort {
    public static void main(String[] args) {
        //测试
        int[] arr = {1, 4, 6, 7, 5, 4, 3, 2, 1, 4, 5, 10, 9, 10, 3};
        sortCount(arr);
        System.out.println(Arrays.toString(arr));//        [1, 1, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 9, 10, 10]
    }

    //计数排序
    public static void sortCount(int[] arr) {
        //一:求取最大值和最小值,计算中间数组的长度:
        int max = arr[0];
        int min = arr[0];
        int len = arr.length;
        for (int i : arr) {
            if (i > max) {
                max = i;
            }
            if (i <h2>18.3 时间复杂度</h2>
  • 最优时间复杂度:o(n+k)
  • 最坏时间复杂度:o(n+k)
  • 稳定性:不稳定

计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法

第19章 桶排序

19.1 桶排序概念

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间 o(n)。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。

排序步骤

  • 设置一个定量的数组当作空桶;
  • 遍历输入数据,并且把数据一个一个放到对应的桶里去;
  • 对每个不是空的桶进行排序;
  • 从不是空的桶里把排好序的数据拼接起来

动图展示

Detailed graphic explanation of Java data structures and algorithms

静图展示
Detailed graphic explanation of Java data structures and algorithms

19.2 代码实现

package sort;import java.util.ArrayList;import java.util.Collections;public class BucketSort {

    public static void main(String[] args) {
        int[] arr = {29, 25, 3, 49, 9, 37, 21, 43};
        bucketSort(arr);
        //分桶后结果为:[[3, 9], [], [21, 25], [29], [37], [43, 49]]
    }

    public static void bucketSort(int[] arr) {
        // 大的当小的,小的当大的
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        // 找出最小最大值
        for (int i=0, len=arr.length; i<len>> bucketArr = new ArrayList(bucketNum);

        // 这一步是不可缺少的,上面的初始化只初始化了一维列表。二维列表需额外初始化
        for (int i=0; i<bucketnum>());
        }
        for (int i=0, len=arr.length; i<len><h2>19.3 时间复杂度</h2>
<ul>
<li>最优时间复杂度:o(n)</li>
<li>最坏时间复杂度:o(n^2)</li>
<li>稳定性:稳定</li>
</ul>
<p>对于桶排序来说,分配过程的时间是O(n);收集过程的时间为O(k) (采用链表来存储输入的待排序记录)。因此,桶排序的时间为<code>O(n+k)</code>。若桶个数m的数量级为O(n),则桶排序的时间是线性的,最优即O(n)。</p>
<p>前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。其次待排序的元素都要在一定的范围内等等。</p>
<h2>第20章 基数排序</h2>
<h2>20.1 基数排序概念</h2>
<p><strong>基数排序</strong>(Radix Sort)是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前</p>
<p><strong>排序步骤</strong>:</p>
<ul>
<li>取得数组中的最大数,并取得位数;</li>
<li>arr为原始数组,从最低位开始取每个位组成radix数组;</li>
<li>对radix进行计数排序(利用计数排序适用于小范围数的特点)</li>
</ul>
<p><strong>动图展示</strong>:</p>
<p><img src="https://img.php.cn/upload/article/000/000/067/e8617e003a5b8a2e40eb3c59220e97a6-113.gif" alt="Detailed graphic explanation of Java data structures and algorithms"><br><strong>静图分析</strong>:</p>
<p><img src="https://img.php.cn/upload/article/000/000/067/e8617e003a5b8a2e40eb3c59220e97a6-114.png" alt="Detailed graphic explanation of Java data structures and algorithms"></p>
<h2>20.2 代码实现</h2>
<pre class="brush:php;toolbar:false">import java.util.Arrays;public class RadixSort {
    public static void main(String[] args) {
        int[] arr = {4, 32, 457, 16, 28, 64};
        radixSort(arr);//        [32, 4, 64, 16, 457, 28]//        [4, 16, 28, 32, 457, 64]//        [4, 16, 28, 32, 64, 457]
    }

    //基数排序
    public static void radixSort(int[] arr) {
        // 定义临时二维数组表示十个桶
        int[][] temp = new int[10][arr.length];
        // 定义一个一维数组,用于记录在temp中相应的数组中存放数字的数量
        int[] counts = new int[10];
        //存数组中最大的数字
        int max = Integer.MIN_VALUE;
        for (int i = 0; i  max) {
                max = arr[i];
            }
        }
        //计算最大数字是几位数(才能知道排序次数)
        int maxLength = (max + "").length();
        //根据最大长度的数决定比较的次数
        for (int i = 0, n = 1; i <h2>20.3 时间复杂度</h2>
  • 最优时间复杂度:O(n^k)
  • 最坏时间复杂度:O(n^k)
  • 稳定性:稳定

初看起来,基数排序的执行效率似乎好的让人无法相信,所有要做的只是把原始数据项从数组复制到链表,然后再复制回去。如果有10个数据项,则有20次复制,对每一位重复一次这个过程。假设对5位的数字排序,就需要20 * 5=100次复制。

*如果有100个数据项,那么就有200 * 5=1000次复制。复制的次数与数据项的个数成正比,即O(n)。这是我们看到的效率最高的排序算法。

不幸的是,数据项越多,就需要更长的关键字,如果数据项增加10倍,那么关键字必须增加一位(多一轮排序)。复制的次数和数据项的个数与关键字长度成正比,可以认为关键字长度是N的对数。因此在大多数情况下,基数排序的执行效率倒退为O(N*logN),和快速排序差不多

推荐学习:《java教程

Term Meaning

The above is the detailed content of Detailed graphic explanation of Java data structures and algorithms. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete