Java算法与数据结构

Posted yhr520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java算法与数据结构相关的知识,希望对你有一定的参考价值。

Java算法与数据结构学习

一.数组

//声明数组
dataType[] arrayRefVar;
//创建数组
arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek};

1.使用自定义类封装数组

public class MyArray{
    private long [] arr;
    //表示数组的有效数据的长度
    private int elements;
    public MyArray(){
        arr =new long [50];
    }
    public MyArray(int maxsize){
        arr =new long[maxsize];
    }
    /**
     * 添加数据
     */
    public void insert(long value){
        arr[elements] =value;
        elemenet++;
    }
    /**
     * 显示数据
     */
    public void display(){
        System.out.println("[");
        for(int i=0;i<elements;i++){
            System.out.println(arr[i]+"  ");
        }
        System.out.println("]");
    }
    /**
     * 根据值查找数据
     */
    public int search(long value){
        int i;
        //顺序搜索
        for(i=0;i<elements;i++){
            if(value==arr[i]){
                break;
            }
        }
        if(i==elements){
            return -1;
        }else{
            return i;
        }
    }
    /**
     * 根据索引查找数据
     */
    public long get(int index){
        if(index>=elements ||index <0){
            throw new ArrayIndexOutOfBoundsException();
        }else{
            return arr[index];
        }
    }
    /**
     * 删除数据
     */
    public void delete(int index){
        if(index>=elements ||index <0){
            throw new ArrayIndexOutOfBoundsException();
        }else{
            //将之后的数据向前一位进行覆盖
           for(int i=0;i<elements;i++){
               arr[index]=arr[index+1];
           }
            elements--;
        }
    }
    /**
     * 更新数据
     */
    public void update(int index,long newvalue){
        if(index>=elements ||index <0){
            throw new ArrayIndexOutOfBoundsException();
        }else{
           for(int i=0;i<elements;i++){
               arr[index]=newvalue;
           }
        }
    }
}

2.有序数组

修改上面的添加方法

    /**
     * 添加数据
     */
    public void insert(long value){
        int i;
        for(i=0;i<elements;i++){
            if(arr[i]>value){//找到比新加的值大的那个数的索引号i
                break;
            }
        }
        //将第i位空出,每一位向后面挪一位
        for(int j=elements;j>i;j--){
            arr[j]=arr[j-1];
        }
        //将新插入的值放入该索引的位置
        arr[i]=value;
        elements++;
    }

3.查找算法

线性查找(从头查到尾)

见上面的查找方法

二分法查找(数组必须是有序数组)

	/**
     * 二分法查找数据
     */
	public int binarySearch(long value){
        int middle=0;
        int low=0;//第一位索引
        int pow=elements;//最后一位索引
        while(true){
            middle=(pow+low)/2;
            if(arr[middle]==value){
                return middle;
            }else if(low >pow){//如果第一位索引大于最后一位的索引,代表搜索结束,没有找到该值
                return -1;
            }else{
                if(arr[middle]>value){
		//如果中间的值比查的值大,说明待查值在前面,所以将最后一位的索引改为中间的索引的前一位
                    pow=middle-1;
                }else{
        //如果中间的值比查的值小,说明待查值在后面,所以将第一位的索引改为中间的索引的后一位
                    low=middle+1;
                }
            }
        }
    }

二.简单排序

1.冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

    /**
	 * 冒泡排序
	 * @param arr
	 */
	public static void sort(long arr[]) {
		long tmp=0;
		for (int i = 0; i < arr.length-1; i++) {//遍历数组
			//依次比较两个相邻的元素,并交换
            for (int j = arr.length-1; j >i; j--) {
				if(arr[j]<arr[j-1]) {
					tmp=arr[j];
					arr[j]=arr[j-1];
					arr[j-1]=tmp;
				}
			}
		}
	}

2.选择排序

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

效率比冒泡排序高一些

	/**
	 * 选择排序
	 * @param arr
	 */
	public static void sort(long[] arr) {
		int k=0;
		long tmp=0;//临时存放点
		for (int i = 0; i < arr.length-1; i++) {
			k=i;
            //从剩余的未排序元素中寻找到最小元素存放到临时存放点
			for (int j = i; j < arr.length; j++) {
				if(arr[j]<arr[k]) {
					k=j;
				}
			}
			tmp=arr[i];
			arr[i]=arr[k];
			arr[k]=tmp;
		}
	}

3.插入排序

每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。插入排序法,算法适用于少量数据的排序,时间复杂度O(n^2)。是稳定的排序方法。

	/**
	 * 插入排序
	 * @param arr
	 */
	public static void sort(long arr[]) {
		for (int i = 1; i < arr.length; i++) {//从第二位开始与前面的进行比较
            for (int j = i; j > 0; j--) {//每一步都将待排序的元素插入到前面排序好的队列中
                if (arr[j] < arr[j - 1]) {
                    long temp = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = temp;
                }
            }
        }
	}

三. 栈和队列

我们是用数组实现的,在定义数组类型的时候,也就规定了存储在栈或队列中的数据类型,如果想存储不同类型的数据声明为Object(在下一节链表中用Object实现)

3.1栈

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出(LIFO, Last In First Out)的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

java 模拟栈实现

public class MyStack {
	//底层实现是数组
	private long [] arr;
	private int top;
	
	public MyStack() {
		arr=new long[10];
		top=-1;
	}
	
	public MyStack(int maxsize) {
		arr=new long[maxsize];
		top=-1;
	}
	
	/**
	 * 添加数据
	 */
	public void push(int value) {
		arr[++top]= value;
	}
	/**
	 * 移除数据
	 */
	public long pop() {
		return arr[top--];
	}
	
	/**
	 * 查看数据
	 */
	public long peek() {
		return arr[top];
	}
	
	/**
	 * 判断是否为空
	 */
	public boolean isEmpty() {
		return top == -1;
	}
	/**
	 * 判断是否满了
	 */
	public  boolean isFull() {
		return top == arr.length-1;
	}
}

3.2 队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。

java模拟单向队列

public class MyQueue {
	//底层使用数组
	private long arr[];
	//有效数据
	private int elements;
	//队头
	private int front;
	//队尾
	private int end;
	public MyQueue() {
		arr =new long [10];
		elements=0;
		front=0;
		end=-1;
	}
	public MyQueue(int maxsize) {
		arr =new long [maxsize];
		elements=0;
		front=0;
		end=-1;
	}
	
	/**
	 * 添加数据,队尾插入
	 */
	public void insert(long value) {
		
		arr[++end]=value;
		elements++;
	}
	/**
	 * 删除数据
	 */
	public long remove() {
		elements--;
		return arr[front--];
	}
	/**
	 * 查看数据,从队头查看
	 */
	public long peek() {
		return arr[front];
	}
	/**
	 * 判断是否为空
	 */
	public boolean isEmpty() {
		return elements==0;
	}
	public boolean isFull() {
		return elements ==arr.length;
	}
}

单项队列如果满了,在进行插入会报错,数组溢出。所以改为循环队列。

java模拟循环队列

修改上述添加和删除方法即可

	/**
	 * 添加数据,队尾插入
	 */
	public void insert(long value) {
		if(end==arr.length -1) {//如果数据满了。就从头开始
			end=-1;
		}
		
		arr[++end]=value;
		elements++;
	}
	/**
	 * 删除数据
	 */
	public long remove() {
		long value =arr[front++];
        /*ps:这里是假清除,此处只是将要删除的数据返回将有效数据但未清除
        若想真正清除可以将long数组改为Object然后用如下方式
        Object value=null;
        value =arr[front];
        arr[front] = null;
        front++
        */
		if(front == arr.length) {//如果队头到末尾,从头开始
			front=0;
		}
		elements--;
		return value;
	}

四. 链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

4.1单向链表

单链表是链表中结构最简单的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。

技术图片

java实现单向链表

/**
 * 节点
 * @author Loserfromlazy
 *
 */
public class Node {
	//数据域
	public Object data;
	//节点域
	public Node next;
	
	public Node(Object value) {
		this.data=value;
	}
	
	/**
	 * 显示
	 */
	public void display() {
		System.out.print(data+" ");
	}
}
/**
 * @author Loserfromlazy
 * 链表
 */
public class LinkList {
	//头节点
	private Node first;
	//节点的个数
	private int size;
	public LinkList() {
		first=null;
	}
	
	/**
	 * 插入一个节点,在头节点进行插入
	 */
	public void insertFirst(Object value) {
		Node node = new Node(value);//待插入的节点
		if(size==0) {
			first= node;
		}else {
			node.next=first;//将新插入的节点的下一个节点指向头节点
			first=node;//将新插入的节点变成头节点,这样就可以实现在头节点之前插入节点
		}
		size++;
	}
	
	/**
	 * 删除一个节点,在头节点进行删除
	 */
	public Node deleteFirst() {
		Node node= first;
		first=node.next;//将头节点的下一个节点变成头节点,这样就可以删除当前头节点
		size--;
		return node;
	}
	
	public void display() {
		Node current =first;
		while(current!=null) {
			current.display();
			current=current.next;
		}
		System.out.println();
	}
	
	/**
	 * 查找结点
	 */
	public Node find(Object value) {
		Node current =first;
		int tempSize=size;
		while(tempSize>0) {
			if(value.equals(current.data)){
				return current;
			}else {
				current=current.next;
			}
			tempSize--;
		}
		
		return null;
	}
	
	/**
	 * 删除节点,根据数据域进行删除
	 */
	public boolean delete(Object value) {
		if(size==0) {
			return false;
		}
		Node current =first;
		Node previous =first;
		while(current.data!=value) {//当查到相等的数据current就是要找的节点
			if(current.data==null) {
				return false;
			}else {
                //指向下一个节点继续查
				previous=current;
				current =current.next;
			}
		}
		if(current==first) {
			first=current.next;
			size--;
		}else {
            //将前一个节点直接指向下一个节点,即跳过current节点,就可以完成删除
			previous.next=current.next;
			size--;
		}
		return true;
	}
}

4.2 双端列表

链表中保存着对最后一个链节点的引用。

对于单项链表,如果想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,然后在尾节点后面插入一个节点。如果多个对尾节点的引用,那么会简单很多。

java实现双端列表

主要不同就是新增了一个尾节点,所以修改insertFirst和deleteFirst方法,新增insertLast方法

/**
 * @author Loserfromlazy
 * 双端链表
 */
public class FirstLastLinkList {
	//头节点
	private Node first;
	//尾节点
	private Node last;
	//节点的个数
	private int size;
	public FirstLastLinkList() {
		first=null;
	}
	
	/**
	 * 插入一个节点,在头节点进行插入
	 */
	public void insertFirst(Object value) {
		Node node = new Node(value);
		if(size==0) {
			first=node;
			last= node;//修改的地方
		}else {
			node.next=first;
			first=node;
		}
		size++;
	}
	/**新增的方法
	 * 插入一个节点,从尾节点进行插入
	 */
	public void insertLast(Object value) {
		Node node = new Node(value);
		if(size==0) {
			first=node;
			last=node;
		}else {
            //将待加入的节点插入到尾节点之后,将新插入的节点变成尾节点
			last.next=node;
			last=node;
		}
		size++;
	}
	/**
	 * 删除一个节点,在头节点进行删除
	 */
	public Node deleteFirst() {
		Node node= first;
		if(first.next==null) {//修改的地方,如果头节点没有下一个节点,那么尾节点也为空
			last=null;
		}else {
			first=node.next;
		}
		
		size--;
		return node;
	}
	
	public void display() {
		Node current =first;
		while(current!=null) {
			current.display();
			current=current.next;
		}
		System.out.println();
	}

4.3 双向链表

我们知道单向链表只能从一个方向遍历,那么双向链表它可以从两个方向遍历。

PS:注意双端链表与双向链表的区别:双端链表只是增加了一个指向尾节点的节点,但这个节点没有向回指的指针。索引不能进行双向遍历。而双向链表在每一个节点都增加了向回指的指针所以可以双向遍历。

java双向链表的实现

package ch4;

/**
 * 节点
 * @author Loserfromlazy
 *
 */
public class DoubleNode {
	//数据域
	public Object data;
	//节点域
	public DoubleNode next;
	//向前的指针
	public DoubleNode previous;
	
	public DoubleNode(Object value) {
		this.data=value;
	}
	
	/**
	 * 显示
	 */
	public void display() {
		System.out.print(data+" ");
	}
}
/**
 * @author Loserfromlazy
 * 双端链表
 */
public class DoubleLinkList {
	//头节点
	private DoubleNode first;
	//尾节点
	private DoubleNode last;
	//节点的个数
	private int size;
	public DoubleLinkList() {
		first=null;
		size=0;
		last=null;
	}
	
	/**
	 * 插入一个节点,在头节点进行插入
	 */
	public void insertFirst(Object value) {
		DoubleNode node = new DoubleNode(value);
		if(size==0) {
			first=node;
			last= node;
            size++;
		}else {
            //将头节点的前一个节点变成新插入的节点,然后将新插入的节点的下一个节点指向头节点,这样就可以实现将头节点与新插入的节点相连,然后将新插入的节点变为头节点即可
			first.previous=node;
            node.next=first;
			first=node;
            size++;
		}
		
	
	}
	/**
	 * 插入一个节点,从尾节点进行插入
	 */
	public void insertLast(Object value) {
		DoubleNode node = new DoubleNode(value);
		if(size==0) {
			first=node;
			last=node;
		}else {
			last.next=node;
			node.previous=last;//将尾节点与新插入的节点相连
			last=node;
		}
		size++;
	}
	/**
	 * 删除一个节点,在头节点进行删除
	 */
	public DoubleNode deleteFirst() {
		DoubleNode node= first;
		if(first.next==null) {
			last=null;
		}else {
            //将头节点的下一个节点的前节点变为空这样就切断了两个节点的联系
			first.next.previous=null;
		}
		first=node.next;
		size--;
		return node;
	}
	/**
	 * 删除节点,从尾部进行删除
	 */
	public DoubleNode deleteLast() {
		DoubleNode node= last;
		if(first.next==null) {
			first=null;
		}else {
            //将尾节点的前一个节点的下一个节点变为空这样就切断了两个节点的联系
			last.previous.next=null;
			
		}
		last=last.previous;
		size--;
		return node;
	}
	public void display() {
		DoubleNode current =first;
		while(current!=null) {
			current.display();
			current=current.next;
		}
		System.out.println();
	}
	
	/**
	 * 查找结点
	 */
	public DoubleNode find(Object value) {
		DoubleNode current =first;
		int tempSize=size;
		while(tempSize>0) {
			if(value.equals(current.data)){
				return current;
			}else {
				current=current.next;
			}
			tempSize--;
		}
		
		return null;
	}
	
	/**
	 * 删除节点,根据数据域进行删除
	 */
	public boolean delete(Object value) {
		if(size==0) {
			return false;
		}
		DoubleNode current =first;
		DoubleNode previous =first;
		while(current.data!=value) {
			if(current.data==null) {
				return false;
			}else {
				current =current.next;
			}
		}
		if(current==first) {
			first=current.next;
			size--;
		}else {
			current.previous.next=current.next;//切断联系
			size--;
		}
		return true;
	}
	public boolean isEmpty() {
		if(size==0) {return true;}
		return false;
	}
}

五. 递归 Recursive

5.1 定义

编程语言中,函数Func(Type a,……)直接或间接调用函数本身,则该函数称为递归函数

递归必须满足三个条件:

  1. 在每一次调用自己时,必须是(在某种意义上)更接近于解;
  2. 必须有一个终止处理或计算的准则。

当边界条件不满足,递归前进,不满足递归返回;

5.2 三角数字

在该数列中第n项由第n-1项加第n项得到:

三角数即正整数前n项和: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78,..n(n+1)/2

比如:1,1+2=3,1+2+3=6........

java实现三角数字

public static int getNumber(int n){//n为第几项
	int total =0;
    while(n>0){
        total=total+n;
        n--;
    }
    return total;
}

java递归实现三角数字

public static int getNumberByRecursive(int n){//n为第几项
    if(n==1){
        return 1;
    }else{
        return n+getNumberByRecursive(n-1);
    }
}

5.3 Fibonacci数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……

java递归方法实现

此方法的空间复杂度O(2^n)越来越高,最终达到最大深度导致栈溢出。一般不用此方法实现斐波那契数列。

/**
* 递归方法实现
* f(n) = f(n - 1) + f(n - 2)
* 最高支持 n = 92 ,否则超出 Long.MAX_VALUE
* @param num n 
* @return f(n) 
*/
public static long Fibonacci(int n) {
    if(n < 1)
        return 0;
    if(n < 3)
        return 1;
    return fibRec(n - 1) + fibRec(n - 2);
}

对上述方法改良:用平推方法实现(此方法不做深究,我这里主要是举递归的例子)

public static long fibLoop(int num) {
    if(num < 1 || num > 92)
        return 0;
    long a = 1;
    long b = 1;
    long temp;
    for(int i = 3; i <= num; i++) {
        temp = a;
        a = b;
        b += temp;
    }
    return b;
}

5.4 汉诺塔

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

技术图片

使用递归思想解决汉诺塔

如果有三个盘子,只要将最上面的两个盘子解决。如果有四个盘子,只要将最上面的三个盘子解决。依次类推无论有多少个盘子,我们都将其看做只有两个盘子。简单来说,递归算法:

  1. 从初始塔座A上移动包含n-1个盘子到中介塔座B上。
  2. 将初始塔座A上剩余的一个盘子(最大的一个盘子)放到目标塔座C上。
  3. 将中介塔座B上n-1个盘子移动到目标塔座C上。

java实现汉诺塔

	/**
	 * 移动盘子
	 * @param topN 移动的盘子数
	 * @param from 起始塔座
	 * @param inter 辅助塔座
	 * @param to	目标塔座
	 */
	public static void doTower(int topN,String from,String inter,String to) {
		if(topN==1) {
			System.out.println("盘子"+topN+"从"+from+"塔座到"+to+"塔座");
		}else {
			doTower(topN-1,from,to,inter);
			System.out.println("盘子"+topN+"从"+from+"塔座到"+to+"塔座");
			doTower(topN-1, inter, from, to);
		}
	}

六. 希尔排序

希尔排序(Shell‘s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

缺陷:假如一个很小的的数据在靠有的排序上,那么要将该数据排序到正确的位置上,则,所有的中间数据都需要向右移动一位。

优点:希尔排序通过加大插入排序中元素之间的间隔

以上是关于Java算法与数据结构的主要内容,如果未能解决你的问题,请参考以下文章

Java排序算法 - 堆排序的代码

买什么数据结构与算法,这里有:动态图解十大经典排序算法(含JAVA代码实现)

以下代码片段的算法复杂度

《图解数据结构与算法》(Java代码实现注释解析算法分析)

有人可以解释啥是 SVN 平分算法吗?理论上和通过代码片段[重复]

Java 垃圾回收 - 收集算法