Java_数据结构和算法

1 综述

本篇文章是一篇读书笔记,里边记录了一些基于java代码实现的数据结构和算法
如果错误,还望大家不吝赐教

1.1 概念

  • 【数据结构】是指数据在计算机内存或者磁盘中的组织形式
  • 【算法】完成特定任务的过程

2 数组

  • 【大O表示法】表示了数据量和操作时间的关系
  • 常见的数组操作时间复杂度
数据类型 操作 时间复杂度
无序数组 插入 O(1)
无序数组 查找 O(N)
无序数组,有序数组 删除 O(N)
有序数组 插入 O(N)
有序数组 二分查找 O(log N)

2.1 二分查找

/**
     * @param key 要查找的值
     * @param arr 查找范围(数组)
     * @return 存在-返回下标,不存在-返回-1
     * @discription 二分查找
     * 二分查找的关键是,数组必须是有序数组
     */
public static Integer BinarySearch(Integer key, Integer[] arr) {
    if (arr == null || arr.length <= 0) {
        return -2;
    }
    int low = 0;
    int high = arr.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (key < arr[mid]) high = mid;
        else if (key > arr[mid]) low = mid;
        else return mid;
    }
    return -1;
}

3 排序

3.1 冒泡排序

时间复杂度O(N2)

大数往后挪

每一次完整的循环可以得到一个剩余未排序的数据中的最大数

/**
     * @description 冒泡排序:大数往后移动
     * @param arr
     * @return
     */
public static Integer[] Bubbling(Integer[] arr){
    Integer temp;
    for(int i=arr.length-1;i>0;i--) {
            for (int j = 0; j < i; j++) {
            if (arr[j] > arr[j+1]) {
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    return  arr;
}

3.2 选择排序

选择排序不同于冒泡排序的区别是:比较的次数是不变样的,但是移动的次数为O(N)

/**
     * @param arr
     * @return
     * @description 选择排序:每次选出最小的放在排头
     */
public static Integer[] chooseSort(Integer[] arr) {
    Integer temp;
    int index;
    for (int i = 0; i < arr.length - 1; i++) {
        index = i;
        for (int j = i; j < arr.length - 1; j++) {
            if (arr[index] > arr[j + 1])
                index = j + 1;
        }
        if (index != i) {
            temp = arr[i];
            arr[i] = arr[index];
            arr[index] = temp;
        }
    }
    return arr;
}

3.3 插入排序

/**
     * @description 插入排序:类似于斗地主抓牌的时候整牌的过程
     * @param arr
     * @return
     */
public static Integer[] insertSort(Integer[] arr) {
    Integer target;
    int i, j;
    for (i = 1; i < arr.length; i++) {
        if (arr[i] < arr[i - 1]) {
            target = arr[i];
            for (j = i - 1; j >= 0 && arr[j] > target; j--) {
                arr[j + 1] = arr[j];
            }
            arr[j + 1] = target;
        }
    }
    return arr;
}

4 栈和队列

栈:先进后出

队列:先进先出

经典示例:计算算数表达式

① 把中缀表达式解析成后缀表达式,运算符入栈

② 计算后缀表达式,操作数入栈

4.1 栈

import java.lang.reflect.Array;


public class MyStack<E> {
    // 栈顶坐标
    private int top;
    // 栈长度
    private int maxSize;
    private E[] stackArr;
    public MyStack(int maxSize){
        this.maxSize = maxSize;
        this.top = -1;
        this.stackArr = (E[]) Array.newInstance(Object.class,maxSize);
    }
    // 入栈
    public void push(E ch){
        stackArr[++top] = ch;
    }
    // 出栈
    public E pop(){
        return stackArr[top--];
    }
    // 查看栈顶元素
    public E peek(){
        return stackArr[top];
    }
    // 判空
    public boolean isEmpty(){
        return top == -1;
    }
    // 判满
    public boolean isFull(){
        return top == (maxSize-1);
    }
    // 栈实际长度
    public int size(){
        return top+1;
    }
    public void display(){
        System.out.println("出栈顺序:");
        while (top>=0){
            System.out.print("->" + pop());
        }
        System.out.println();
    }
}

中缀表达式转换成后缀表达式

public class InToPostfixExpression {
    private MyStack<Character> myStack;
    private String inPut;
    private String outPut="";
    public InToPostfixExpression(String inPut){
        this.inPut = inPut;
        int stackSize = inPut.length();
        this.myStack = new MyStack(stackSize);
    }
    public String doPost(){
        for(int i=0;i<inPut.length();i++){
            char ch = inPut.charAt(i);
            switch (ch){
                case '-':
                case '+':
                    dealOption(ch,1);
                    break;
                case '*':
                case '/':
                    dealOption(ch,2);
                    break;
                case '(':
                    myStack.push('(');
                    break;
                case ')':
                    dealBrackets();
                    break;
                default:
                    outPut += ch;
            }
        }
        while (!myStack.isEmpty()){
            outPut += myStack.pop();
        }
        outPut = outPut.replaceAll("  "," ");
        return outPut;
    }

    private void dealOption(char thisOption,int grade){
        int oldGrade;
        while (! myStack.isEmpty()){
            char topOption = myStack.pop();
            if(topOption == '('){
                myStack.push(topOption);
                break;
            }else{
                if(topOption == '-' || topOption == '+')
                    oldGrade = 1;
                else
                    oldGrade =2;

                if(oldGrade < grade){
                    myStack.push(topOption);
                    break;
                }else
                    outPut += topOption;
            }

        }
        myStack.push(thisOption);
    }

    public void dealBrackets(){
        while (! myStack.isEmpty()){
            char topOption = myStack.pop();
            if(topOption == '('){
                break;
            }else {
                outPut += topOption;
            }
        }
    }

    public String getOutPut() {
        return outPut;
    }
    
}

计算后缀表达式

public class Calculate {
    private String input;
    private Integer result;
    private MyStack<Integer> myStack;
    public Calculate(String input){
        int maxSize = input.length();
        this.input = input;
        this.myStack = new MyStack(maxSize);
    }

    public Integer doCalculate(){
        int num1;
        int num2;
        for(int i=0;i<input.length();i++){
            char opThis = input.charAt(i);
            if(opThis >= '0' && opThis <= '9'){
                myStack.push(Integer.parseInt(String.valueOf(opThis)));
            }else {
                num2 = myStack.pop();
                num1 = myStack.pop();
                switch (opThis){
                    case '+':
                        result = num1 + num2;
                        break;
                    case '-':
                        result = num1 - num2;
                        break;
                    case '*':
                        result = num1 * num2;
                        break;
                    case '/':
                        if(num2 != 0){
                            result = num1 / num2;
                            break;
                        }else {
                            result = null;
                        }
                        break;
                    default:
                        result=0;
                }
                myStack.push(result);
            }
        }
        return result;
    }
}

4.2 队列

public class MyQueue<E> {
    private int front;          // 队首
    private int rear;           // 队尾
    private E[] queueArr;       // 存储结构-数组
    private int maxSize;        // 队列最大长度
    private int nItems;         // 队列中数据项个数

    public MyQueue(int size){
        this.maxSize = size;
        queueArr = (E[]) Array.newInstance(Object.class,size);
        front=0;
        rear = -1;
        nItems =0;
    }


    public void insert(E item){
        if(rear == maxSize -1){
            rear = -1;
        }
        queueArr[++rear] = item;
        nItems ++;
    }

    public E remove(){
        E result = queueArr[front++];
        if(front == maxSize -1)
            front = 0;
        nItems --;
        return result;
    }

    public E peekFront(){
        return queueArr[front];
    }

    public boolean isEmpty(){
        return nItems == 0;
    }
    public boolean isFull(){
        return nItems == maxSize;
    }
    public int size(){
        return nItems;
    }

    public void display(){
        System.out.println("MQ-QUEUE: Front->Rear");
        Arrays.asList(queueArr).stream().forEach(item -> System.out.print("->"+item));
        System.out.println();
    }
}

5 链表

  • 链表由一个个链接点链接而成,每一个链接点包含两部分:数据项和next(指向下一个链接点的索引)
  • 单链表,只可头插或尾插
  • 双端链表,头尾可查,头可取[{头插头取 - > 栈},{尾插头取 - > 队列}]
  • 双向链表,一个节点有前后两个索引

5.1 链接点类

import java.lang.reflect.InvocationTargetException;

public class Link1<E> {
    public E item;
    public Link1<E> next;
    public Link1(E item) throws IllegalAccessException, InstantiationException {
        this.item = item;
    }
    public void displayLink() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.print(" { "+item.getClass() .getMethod("display").invoke(item)+" }");
    }
}

5.2 单链表

import java.lang.reflect.InvocationTargetException;

public class LinkList1<E> {
    private Link1<E> first;
    public LinkList1(){
        first = null;
    }
    public boolean isEmpty(){
        return first == null;
    }
    public void insertLink(E item) throws InstantiationException, IllegalAccessException {
        Link1 newLink = new Link1(item);
        newLink.next = first;
        first = newLink;
    }
    public Link1 deleteFirst(){
        Link1 temp = first;
        first = first.next;
        return temp;
    }

    public void displayList() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        System.out.println("LINK-LIST: REAR -> FRONT");
        Link1 current = first;
        while (current != null){
            current.displayLink();
            current = current.next;
        }
    }
}

5.3 双端链表

import java.lang.reflect.InvocationTargetException;

public class DoubleEndedLinkedList<E> {
    private Link1<E> firstLink;
    private Link1<E> lastLink;
    public DoubleEndedLinkedList(){
        firstLink = null;
        lastLink = null;
    }

    public boolean isEmpty(){
        return firstLink == null;
    }

    public void insertFirst(E item) throws InstantiationException, IllegalAccessException {
        Link1 link1 = new Link1(item);
        if(isEmpty())
            lastLink = link1;
        link1.next = firstLink;
        firstLink = link1;
    }

    public void insertLast(E item) throws InstantiationException, IllegalAccessException {
        Link1 link1 = new Link1(item);
        if(isEmpty()) firstLink = link1;
        else lastLink.next = link1;
        lastLink = link1;
    }

    public E delFirst(){
        E item = firstLink.item;
        if(firstLink.next == null)
            lastLink = null;
        firstLink = firstLink.next;
        return item;
    }

    public void display() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        System.out.println("First -> Last");
        Link1 current = firstLink;
        while (current!=null){
            current.displayLink();
            current = current.next;
        }
        System.out.println();
    }
}

5.4 有序链表

5.4.1 链接点

public class LinkInteger {
    private Integer key;
    public LinkInteger next;
    public LinkInteger(Integer item){
        this.key = item;
        next = null;
    }

    public Integer getKey() {
        return key;
    }

    public void displayNode(){
        System.out.print(key+" ");
    }
}

5.4.2 带有排序和删除的单链表

public class SortLink {
    private LinkInteger first;
    public SortLink(){
        first = null;
    }
    public boolean isEmpty(){
        return first == null;
    }
    public void insert(Integer item) {
        LinkInteger newLink = new LinkInteger(item);
        LinkInteger previous = null;
        LinkInteger current = first;

        while ((current != null) && item > current.getKey()){
            previous = current;
            current = current.next;
        }
        if(previous==null) {
            first = newLink;
        }else {
            previous.next = newLink;
        }
        newLink.next = current;
    }

    public LinkInteger remove(){
        LinkInteger temp = first;
        first = first.next;
        return temp;
    }
    public void display() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        System.out.println("LINK-LIST: REAR -> FRONT");
        LinkInteger current = first;
        while (current != null){
            current.displayNode();
            current = current.next;
        }
    }
}

5.5 双向链表

5.5.1 链接点

public class DoublyLinkNode {
    private Integer key;
    public DoublyLinkNode previous;
    public DoublyLinkNode next;
    public DoublyLinkNode(Integer key){
        this.key = key;
    }

    public Integer getKey() {
        return key;
    }

    public void display(){
        System.out.print(key+" ");
    }
}

5.5.2 带有前插、尾插、有序插、头删、尾删、指定删除

public class DoublyLinkList {
    private DoublyLinkNode first;
    private DoublyLinkNode last;
    public DoublyLinkList(){
        first = null;
        last = null;
    }

    public boolean isEmpty(){
        return first == null;
    }

    public void insertFirst(Integer key){
        DoublyLinkNode newNode = new DoublyLinkNode(key);
        if(isEmpty())
            last = newNode;
        else
            first.previous = newNode;
        newNode.next = first;
        first = newNode;
    }

    public void insertLast(Integer key){
        DoublyLinkNode newNode = new DoublyLinkNode(key);
        if(isEmpty())
            first = newNode;
        else{
            last.next = newNode;
            newNode.previous = last;
        }
        last = newNode;
    }

    public void orderInsert(Integer key){
        DoublyLinkNode newNode = new DoublyLinkNode(key);
        DoublyLinkNode temp = null;
        DoublyLinkNode current = first;
        if(first == null){
            first = newNode;
        }else {
            while (current != null && current.getKey() <= key){
                temp = current;
                current = current.next;
            }
            if(temp == null){
                current.previous = newNode;
                newNode.next = current;
                first = newNode;
            }else if(current == null){
                temp.next = newNode;
                newNode.previous = temp;
                last = newNode;
            }else{
                temp.next = newNode;
                newNode.previous = temp;
                newNode.next = current;
                current.previous = newNode;
            }
        }
    }

    public DoublyLinkNode delFirstNode(){
        DoublyLinkNode temp= first;
        if(first.next == null)
            last = null;
        else
            first.next.previous = null;
        first = first.next;
        return temp;
    }
    public DoublyLinkNode delLastNode(){
        DoublyLinkNode temp = last;
        if(last.previous == null)
            first = null;
        else {
            last.previous.next = null;
        }
        last = last.previous;
        return temp;
    }
    public DoublyLinkNode delKey(Integer key){
        DoublyLinkNode current = first;
        while (current.getKey() != key){
            current = current.next;
            if(current == null)
                return null;
        }
        if(current .equals(first)){
            first = current.next;
        }else {
            current.previous.next = current.next;
        }

        if(current.equals(last)){
            last = current.previous;
        }else {
            current.next.previous = current.previous;
        }
        return current;
    }

    public void displayForward(){
        DoublyLinkNode current = first;
        System.out.println("display by forward:");
        while (current!=null){
            current.display();
            current = current.next;
        }
        System.out.println();
    }

    public void displayBackward(){
        DoublyLinkNode current = last;
        System.out.println("display by backward:");
        while (current!=null){
            current.display();
            current = current.previous;
        }
        System.out.println();
    }
}

6 ADT列表

抽象数据类型(ADT,Abstract Data Type)可以先理解什么是数据类型,然后来理解抽象。

int,double都属于数据类型,数据类型一般涉及两个部分:数据(拥有特定数据项的集合)+操作(在数据上可以完成的动作)

对于Java来说,数据类型能很好的对应成类,成员变量(数据)+方法(操作)

列表:也叫做线性表,是一组线性排列的数据项。它是一种抽象,可以通过链表和数组实现

7 递归

  • 可用来解决特定问题:三角数字、汉诺塔、阶乘、生成变位字

  • 简单来说,递归就是自己调用自己

  • 递归可以转换成一个基于栈的非递归方法

  • 采用递归往往是从概念上简化了问题,而不是因为本质上提高了效率

7.1 三角数字

一个数字序列:1、3、6、10、15、21、、、
第 N 项 = 第 ( N − 1 ) 项 + N 或 者 第 N 项 = 1 + 2 + 3 + . . . + N 第N项 = 第(N-1)项 + N 或者 第N项 = 1 + 2 + 3 + ... + N N=(N1)+NN=1+2+3+...+N

/**
     * @description 求第N个三角数字
     * @param n
     * @return
     */
public static int triangle(int n){
    if(n == 1){
        return 1;
    }else {
        return (n + triangle(n-1));
    }
}

7.2 阶乘

    /**
     * @description 求N的阶乘
     * @param n
     * @return
     */
    public static int factorial(int n){
        if(n==0){
            return 1;
        }else {
            return (n * factorial(n-1));
        }
    }

7.3 变位字(求全排列)

7.4 二分查找

/**
     * @description 递归-二分查找
     * @param arr 已排序的数组
     * @param key 要查询的值
     * @param lowerBound [lowerBound,upperBound]
     * @param upperBound
     * @return
     */
public static int binarySearch(int[] arr,int key,int lowerBound,int upperBound){
    int currentIndex = lowerBound+(upperBound-lowerBound)/2;
    if(lowerBound > upperBound){
        return -1;
    }
    if(arr[currentIndex] == key){
        return currentIndex;
    }
    if(arr[currentIndex] > key){
        return binarySearch(arr,key,lowerBound,currentIndex-1);
    }

    if(arr[currentIndex] < key){
        return binarySearch(arr,key,currentIndex+1,upperBound);
    }
    return -1;
}

7.5 汉诺塔问题

汉诺塔:共需要移动 2n -1

/**
     * @description 汉诺塔问题
     * @param dishNum
     * @param from
     * @param temp
     * @param to
     */
public static void dealHanoi(int dishNum,char from,char temp,char to){
    if(dishNum == 1){
        System.out.println("dishNum 1 from "+from+" to "+to);
    }else {
        dealHanoi(dishNum-1,from,to,temp);
        System.out.println("dishNum " +dishNum + " from "+from+" to "+to);
        dealHanoi(dishNum-1,temp,from,to);
    }

}

7.6 归并排序

/**
     * @description 归并排序
     * @param arrA  原数组A
     * @param arrB  原数组B
     * @param arrC  归并后有序数组C
     * @param sizeA 数组A的长度
     * @param sizeB 数组B的长度
     */
public static Integer[] mergeSort(int[] arrA,int[] arrB,Integer[] arrC,int sizeA,int sizeB){
    int indexA=0,indexB=0,indexC=0;
    while(indexA < sizeA && indexB <sizeB){
        if(arrA[indexA] < arrB[indexB]){
            arrC[indexC++] = arrA[indexA++];
        }else {
            arrC[indexC++] = arrB[indexB++];
        }
    }
    while (indexA < sizeA){
        arrC[indexC++] = arrA[indexA++];
    }
    while (indexB < sizeB){
        arrC[indexC++] = arrB[indexB++];
    }
    return arrC;
}

8 两种快速排序算法

希尔排序、快速排序

8.1 希尔排序

8.1.1 原理解析

希尔排序的时间复杂度是O(N * (logN)2)。

原理:

① 使用 h = h ∗ 3 + 1 h = h*3 + 1 h=h3+1来获取最大间隔h,

② 做增量为h的增量排序,直到h递减到1,

③ 每次增量排序后,都达到“基本有序

8.1.2 代码

/**
 * @description 希尔排序
 *      由来:插入排序的弊端是,移动的步长是1,如果元素a要插入到长度为N的升序数组中,元素a要移动N次
 *      希尔排序中定义了一个数列:h=h*3+1;
 * @param arr
 * @return
 */
public static Integer[] shellSort(Integer[] arr){
    int outer,inner,h=1;
    Integer temp;
    // 1 求出最大步长
    while (h <= arr.length/3)
        h = h*3 +1;
    // 2 排序
    while (h>0){
        for(outer=h;outer<arr.length;outer++){
            inner = outer;
            temp = arr[outer];
            while (inner >h-1 && arr[inner-h]>temp){
                arr[inner] = arr[inner-h];
                inner -= h;
            }
            arr[inner] = temp;
        }
        h = (h-1)/3;
    }
    return arr;
}

8.2 快速排序

8.2.1 原理解析

时间复杂度:O(N * logN)

核心思想是:递归调用划分算法

8.2.2 代码示例

/**
     * * 快速排序
     * * 核心思想:划分算法
     * *
     */
public static Integer[] quickSort(Integer[] arr,int minIndex,int maxIndex){
    if(maxIndex-minIndex<=0){
        return arr;
    }else{
        Integer pivot = arr[maxIndex];
        int partition = partitionIt(arr,minIndex,maxIndex,pivot);
        quickSort(arr,minIndex,partition-1);
        quickSort(arr,partition+1,maxIndex);
    }
    return arr;
}

public static int partitionIt(Integer[] arr,int minIndex,int maxIndex,Integer pivot){
    int leftPtr = minIndex -1;
    int rightPtr = maxIndex;
    while (true){
        while (arr[++leftPtr]<pivot);
        while (rightPtr>0 && arr[--maxIndex]>pivot);
        if(leftPtr>=rightPtr){
            break;
        }else {
            swap(arr,leftPtr,rightPtr);
        }
    }
    swap(arr,leftPtr,maxIndex);
    return leftPtr;
}

private static void swap(Integer[] arr, int leftPtr, int rightPtr) {
    Integer temp = arr[leftPtr];
    arr[leftPtr] = arr[rightPtr];
    arr[rightPtr] = temp;
}

9 树

9.1 二叉树

9.1.1 什么是二叉树

  • 树由边连接点节点构成
  • 如果一个树中每个节点最多只有两个子节点,那么这种树叫做二叉树
  • 二叉搜索树:一个二叉树的左子节点小于该节点,右子节点大于该节点

9.1.2 为什么使用二叉树

二叉树兼并了两种数据结构的优点:可以像有序数组一样快速查找 + 像链表一样快速插入和删除

9.2 二叉搜索树

  • 二叉搜索树就是在二叉树的基础上,它的左子节点小于本身,它的右节点大于等于它本身

9.3 红黑树

  • 当要插入的数据有序的,二叉搜索树会变成一条线,从而降低效率
  • 红黑树则可以解决这个问题,让树变平衡

9.4 2-3-4树、2-3树、B-树

  • 这三种树都是多叉树
  • 2-3-4树每个节点最多有三个数据项,4个子节点
  • B-树多用于外部存储
  • 外部存储区别于主存(内存),相较于主存:它更便宜(每字节)、速度慢

10 哈希表

  • 哈希表是基于数组实现的
  • 哈希函数就是解决把较大的关键字值压缩到较小的数组下标
  • 冲突:一个关键字哈希到已被占有的单元,这种情况叫做冲突
  • 通常有3种方法解决解决冲突:开发地址法、链地址法、哈希函数
  • 开放地址法:尝试解决发生聚集(冲突)的问题。
    1. 线性探索,发生聚集时,依次探索X+1,X+2,X+3,X+4… 步
    2. 二次探索,发生聚集时,依次探索X+12,X+22,X+32,X+42,… 步
    3. 再哈希法,使用新的哈希函数,生成基于关键字的且不为0的探测序列
  • 链地址法:数组的每一个元素都存放一个链表,当发生聚集时,可以接在指定下标所在的链表后

END

你可能感兴趣的:(java,数据结构,算法,java)