Java实现数据结构

1.链表

   链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

代码实现:
private class Node :结点,方便将各个结点连接成链表
private class Node{
	//	存储数据
	T item;
	//	下一个结点
	Node next;
	public Node(T item, Node next) {
		this.item = item;
		this.next = next;
	}
	public Node() {};
}

public class LinkList<T> {
	//	记录头结点  
	private Node head;
	//	记录链表的长度
	private int N;
	//	结点类
	private class Node{
		//	存储数据
		T item;
		//	下一个结点
		Node next;
		public Node(T item, Node next) {
			this.item = item;
			this.next = next;
		}
		public Node() {};
	}
	public LinkList(){
		//	初始化头结点
		this.head = new Node();
		//	初始化元素个数
		this.N = 0;
	}
	//	清空链表
	public void clear() {
		this.head = null;
		this.N = 0;
	}
	//	获取链表的长度
	public int length() {
		return N;
	}
	//判断链表是否为空
	public boolean isEmpty() {
		return N == 0;
	}
	//获取指定位置i处的元素
	public T get(int i) {
		//	通过循环,从头结点开始往后查找
		Node node = head;
		for (int j = 0; j <= i; j++) {
			node = node.next;
		}
		return node.item;
	}
	//向链表中添加元素t
	public void insert(T t) {
		//找到当前最后一个结点
		Node last = head;
		while(last.next!=null)
			last = last.next;
		//创建新结点,保存元素t
		Node now = new Node(t,null);
		//让当前最后一个结点指向新结点
		last.next = now;
		//	让元素个数加一
		this.N++;
	}
	//向指定位置i处添加元素t
	public void insert(int i, T t) {
		Node pre = head;
		//找到i位置的前一个结点
		for (int j = 0; j < i; j++) {
			pre = pre.next;
		}
		//找到i位置的结点
		Node cur = pre.next;
		//创建新结点,前一结点指向新结点,新结点指向原来i位置的结点
		Node now = new Node(t, cur);
		pre.next = now;
		//元素个数加一
		this.N++;
	}
	//删除指定位置i的元素,返回被删除的元素
	public T remove(int i) {
		//找到i位置前一个结点
		Node pre = head;
		for (int j = 0; j < i; j++) {
			head = head.next;
		}
		//找到i位置的结点并保存
		Node cur = pre.next;
		//找到i位置下一个结点
		Node next = cur.next;
		//前一个结点指向下一个结点
		pre.next = next;
		//元素个数减一
		this.N--;
		return cur.item;
	}
	//查找元素i在链表中第一次出现的位置
	public int indexOf(T t) {
		//	从头开始依次查找每一个结点,取出item,和t比较,如果相同则找到了
		Node now = head;
		for (int i = 0; now.next!= null; i++) {
			now = now.next;
			if (now.item==t) {
				return i;
			}
		}
		return -1;
	}
}

##代码测试
public class LinkListTest {
	public static void main(String[] args) {
		//	创建单向链表对象
		LinkList<String> s1 = new LinkList<>();
 		//	测试插入
		s1.insert("姚明");
		s1.insert("科比");
		s1.insert("麦迪");
		s1.insert(1,"詹姆斯");
		for (int i = 0; i < s1.length(); i++) {
			System.out.println(s1.get(i));
		}
		System.out.println("-----------------------------");
		//	测试获取
		String getres = s1.get(1);
		System.out.println("获取索引1处的结果为:" + getres);
		//	测试删除
		String removeRes = s1.remove(0);
		System.out.println("删除的元素为:"+removeRes+"\t删除后长度为:"+s1.length());
		//测试返回元素第一次出现的索引
		System.out.println("麦迪第一次出现的索引为:"+s1.indexOf("麦迪"));
		//	测试清空
		s1.clear();
		System.out.println("清空后链表中元素的个数为:"+s1.length());
	}
}

双链表

双链表也叫双向链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

代码实现
private class Node{
	public T item;
	public Node pre;//前一个结点
	public Node next;//后一个结点
	public Node(T item, Node pre, Node next) {//构造方法
		this.item = item;
		this.pre = pre;
		this.next = next;
	}
}
public class TwoWayLinkList<T> {
	//首结点
	private Node head;
	//尾结点
	private Node last;
	//链表长度
	private int N;
	//结点类
	private class Node{
		public T item;
		public Node pre;
		public Node next;
		public Node(T item, Node pre, Node next) {
			this.item = item;
			this.pre = pre;
			this.next = next;
		}
	}
	
	public TwoWayLinkList() {
		//初始化头结点和尾结点
		this.head = new Node(null,null,null);
		this.last = null;
		//初始化元素个数
		this.N = 0;
	}
	//清空链表
	public void clear() {
		this.head.next = null;
		this.last = null;
		this.N = 0;
	}
	//获取链表长度
	public int length() {
		return this.N;
	}
	//判断链表是否为空
	public boolean isEmpty() {
		return N == 0;
	}
	//获取第一个元素
	public T getFirst() {
		if (isEmpty()) {
			return null;
		}
		return head.next.item;
	}
	//获取最后一个元素
	public T getLast() {
		if (isEmpty()) {
			return null;
		}
		return last.item;
	}
	//插入元素t
	public void insert(T t) {
		//如果链表为空
		if (isEmpty()) {
			//创建新结点
			Node newNode = new Node(t,head,null);
			//让新结点称为尾结点
			last = newNode;
			//让头结点指向尾结点
			head.next = last;
		}else {//如果不为空
			//创建新结点
			Node newNode = new Node(t,last,null);
			//让当前尾结点指向新结点
			last.next = newNode;
			//让新结点成为尾结点
			last = newNode;
		}
		//元素个数加一
		this.N++;
	}
	//指定位置插入元素t
	public void insert(int i, T t) {
		//找到i位置的前一个结点
		Node pre = head;
		for (int j = 0; j < i; j++) {
			pre = pre.next;
		}
		//找到i位置的结点
		Node cur = pre.next;
		//创建新结点
		Node newNode = new Node(t,pre,cur);
		//让前一结点的next变为新结点
		pre.next = newNode;
		//让i位置的前一结点变为新结点
		cur.pre = newNode;
		//元素个数加一
		this.N++;
	}
	//获取指定位置的元素
	public T get(int i) {
		Node now = head.next;
		for (int j = 0; j < i; j++) {
			now = now.next;
		}
		return now.item;
	}
	//找到第一次出现t的位置
	public int indexOf(T t) {
		Node now = head;
		for (int i = 0; now.next != null; i++) {
			now = now.next;
			if (now.item.equals(t)) {
				return i;
			}
		}
		return -1;
	}
	//删除i处的元素,并返回其元素
	public T remove(int i) {
		//找到前一个结点
		Node pre = head;
		for (int j = 0; j < i; j++) {
			pre = pre.next;
		}
		//找到i位置的下一个结点
		Node cur = pre.next;
		//找到下一个结点
		Node nextNode = cur.next;
		//让前一个结点的next指向下一个结点
		pre.next = nextNode;
		//让下一个结点的pre指向前一个结点
		nextNode.pre = pre;
		//元素个数减一
		this.N--;
		return cur.item;
	}
}

2.栈

栈只允许访问一个数据项:即最后插入的数据项。移除这个数据项后才能访问倒数第二个插入的数据项,以此类推。大部分的微处理器运用基于栈的体系结构,当待用一个方法时,把它的返回地址和参数压入栈,当方法结束返回时那些数据出栈,栈操作就嵌入在微处理器中。

代码实现
/*
* 01 数组实现的栈
* push 、remove、 peek 入栈、出栈、查看栈顶值
* isFull isEmpty size 判断栈满、空、大小
* */
public class StackX {
    private int maxSize;
    private int top; // 栈顶指针
    private long[] stackArray;
	
    public StackX(int maxSize) {
        this.maxSize = maxSize;
        this.top = -1;
        this.stackArray = new long[maxSize];
    }

    public void push(long item) throws Exception {
        if (isFull()) throw new Exception("栈满");
        stackArray[++top]=item;
    }
    public long remove() throws Exception {
        if (isEmpty()) throw new Exception("栈空");
        return stackArray[top--];
    }

    public long peek(){
        return stackArray[top];
    }
    public boolean isFull(){
        return top+1==maxSize;
    }
    public boolean isEmpty(){
        return top==-1;
    }
    public int size(){
        return top+1;
    }

    @Override
    public String toString() {
        return "StackX{" +
                "maxSize=" + maxSize +
                ", top=" + top +
                ", stackArray=" + Arrays.toString(stackArray) +
                '}';
    }
}

##代码测试
public class StackTest {
    public static void main(String[] args) throws Exception {
        StackX stack=new StackX(5);
        stack.push(50);
        stack.push(20);
        stack.push(30);
        System.out.println("stack.peek():"+stack.peek());
        stack.push(40);
        stack.push(10);
        System.out.println(stack);
        int j=stack.size();
        for (int i = 0; i < j; i++) {
            System.out.println(stack.remove());
        }
    }
}

3.对列

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

代码实现
public class Queue {
    private int maxSzie; //队列中能存储的最大个数
    private int frontPoint; //头指针指向队头
    private int rearPoint; //尾指针指向队尾的后一个数据
    private int[] array; //模拟队列的数组

    /**
     * 初始化队列
     */
    public Queue(int max) {
        maxSzie = max;
        frontPoint = 0;
        rearPoint = 0;
        array = new int[max];
    }

    /**
     * 判断队列是否为空
     */
    public boolean isEmpty(){
        return frontPoint == rearPoint;
    }
    /**
     * 判断队列是否已满
     */
    public boolean isFull(){
        return (rearPoint+1)%maxSzie == frontPoint;
    }
    /**
     * 向队列中添加数据
     */
    public void add(int x){
        if (isFull()){
            System.out.println("当前队列已满");
            return;
        }
        //添加数据
        array[rearPoint] = x ;
        //后移尾指针
        rearPoint = (rearPoint+1) % maxSzie;
        System.out.println("添加成功");
    }
    /**
     * 取出队列中的数据
     */
    public int remove(){
        if (isEmpty()){
            throw new RuntimeException("当前队列为空");
        }
        //把队头的值赋值给临时变量
        int x = array[frontPoint];
        //移除数据后头指针需要向后移动 时其指向新的队头
        frontPoint = (frontPoint+1) % maxSzie;
        System.out.println("移除成功");
        return x;
    }
    /**
     * 获取队列头数据
     */
    public int gethead(){
        if (isEmpty()){
            throw new RuntimeException("当前队列为空");
        }
        return array[frontPoint];
    }
    /**
     * 遍历队列
     */
    public void show(){
        int x = 0;
       for (int i = frontPoint; i <= (rearPoint+maxSzie-frontPoint)%maxSzie; i++) {
            x++;
            System.out.println("队列的第"+x+"个数据是"+array[i]);
        }
    }
}

#代码测试
public class QueueTest {
    public static void main(String[] args) {
        Queue queue = new Queue(5);
        Scanner scanner = new Scanner(System.in);
        char systemIn = ' ';
        boolean noEnd = true;
        while (noEnd){
            System.out.println("a:add(添加数据)");
            System.out.println("r:remove(删除数据)");
            System.out.println("h:head(获取队头)");
            System.out.println("s:show(遍历队列)");
            System.out.println("e:exit(退出程序)");
            System.out.println("请输入字符");
            systemIn = scanner.next().charAt(0);
            switch (systemIn){
                case 'a':
                    System.out.println("请输入入队的数据(数字)");
                    int x = Integer.parseInt(scanner.next());
                    queue.add(x);
                    break;
                case 'r':
                    queue.remove();
                    break;
                case 'h':
                    int head = queue.gethead();
                    System.out.println("队头是"+head);
                    break;
                case 's':
                    queue.show();
                    break;
                case 'e':
                    noEnd = false;
                    break;
            }
        }
    }
}

4.散列表

对于一个相对较大的任意长度的数据,而且这个数据可能不是存储在连续空间,把这个数据映射到一个相对较小空间的数组里,这里面提到的较小空间的数组就是一个散列表,也可以叫做“哈希表”或者“Hash表”,这个实现映射的过程就是一个散列函数,可以用 hash(key)=value 来表示。散列表的本质是一个数组,可以在O(1)的时间复杂度查找元素。
散列表的优势:可以非常快速的插入、删除、查找元素,无论多少数据,插入和删除的时间复杂度都接近常量;散列表的查找速度比树还要快。散列表的不足之处:散列表中的数据不是有序的,所以不能以一种固定的方式(比如从小到大)来遍历其中的元素,而且散列表中的key是不允许重复的。总结就是:散列表实现了关键字到数组地址的映射,可以在常数时间复杂度内通过关键字查找到数据。

一般可以参考下面几种方式来设计一个散列函数:
直接寻址法:哈希函数为关键字到地址的线性函数。如 F(key)=a*key+b,这里 a和b是设置好的常数。
数字分析法:假设关键字集合中的每个关键字key都是由s位数字组成(k1, k2, …, Ks),可以从中提取分布均匀的若干位组成哈希地址,比如手机号后四位作为散列值。
平方取中法:如果关键字的每一位都有某些数字重复出现,并且频率很高,可以先求关键字的平方值,通过平方扩大差异,然后取中间几位作为最终存储地址。
折叠法:如果关键字的位数很多,可以将关键字分割为几个等长的部分,取它们的叠加和的值(舍去进位)作为哈希地址。
计算余数法:预先设置一个数x, 然后对关键字进行取余运算,即 key % x,也就是上面图中所示的方法。

散列函数特点
在同一个散列函数中输入一个任意的原始数据,都可以得到一个相应的哈希值;
在同一个散列函数中输入两个相同的原始数据,它们总会得到相同的哈希值;
在同一个散列函数中输入两个不同的原始数据,它们也有可能得到相同的哈希值,也就是:散列冲突,比如上面图中的余数为2的就存在两个原始数据。

散列冲突
不同的两个或者多个原始数据经过散列函数计算后,是有可能得到一个相同的哈希值,这就是散列冲突。因为数组的存储空间有限,也会加大散列冲突的概率。
常用的散列冲突解决方法有两类,分别是:开放寻址法 和 链表法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值