Java数据结构(一)——链表

Java中的数据结构又很多种,如栈,队列,树等,但是他们的形式归根到底就是两种:一个是数组,一个是链表,所有的结构都是对这两个的变形。

什么是线性表?

数组和链表都属于是线性表,那什么是线性表:一个线性表是n个相同特性的数据元素的有序序列。各元素之间是一对一的关系。但这并不是说一个线性表除了第一个和最后一个其他都是首尾相接,因为循环链表也是一种线性表。因为这个线性关系针对的是逻辑上的。
数组,是物理上连续的一块存储空间,它便于根据索引直接取出数据和存入数据,但是搜索和查找的效率低下,插入和删除的效率低下,并且数组一旦创建以后大小就固定,如果存储的数据太少,空间就浪费,如果存储的数据太多,那就不能满足,于是有了链表的产生。
链表,不能解决数据存储的所有问题,一般需要频繁使用下标查找访问数据的地方都不使用链表。链表在内存中存储的位置是随机地,但是通过指针相互连接,形成一种线性关系,所以链表是一种线性表。
对于链表,无论是哪种形式,我们希望能实现这些基础功能:

//创建结点类
//添加节点(只能头部插入)
//查找数据
//删除数据
//打印数据
//计算长度

下面我们将简述4种链表的实现方式:

1. 单向链表
2. 双向链表
3. 有序链表
4. 有序链表优化无序数组的排序
5. 双端链表

1.单向链表

一个单向链表的结点分成两部分:数据+下一结点的地址。
特性:只能单向遍历,只提供链表头插入
在这里插入图片描述

单向链表的具体实现:

package com.lm.list1118;

public class SingleLinklist {
	//创建结点类
	public class Node{
		Object data;//存储数据
		Node next;//指向下一节点的地址
		public Node(Object data){//构造函数
			this.data = data;
		}
	}
	//创建头结点
	private Node head;
	
	//添加节点(只能头部插入)
	public void addNode(Object data) {
		Node node = new Node(data);
		if(head == null) {//当链表为空时
			head = node;
		}else {
			node.next = head;
			head = node;
		}
	}
	//查找指定数据
	public Node findNode(Object data) {
		Node tmp = head;
		while(tmp != null) {
			if(data.equals(tmp.data)) {
				return tmp;
			}
			tmp = tmp.next;
		}
		return null;
	}
	//删除数据(删除头结点)
	public boolean deleteHead() {
		while(head.next != null) {
			head = head.next;
			return true;
		}
		return false;
	}
	//删除数据(删除指定元素)
	public boolean deleteNode(Object data) {
		if(data.equals(head.data)) {
			head = head.next;
			return true;
		}
		Node tmp = head.next;
		Node curNode = head;
		while(tmp != null) {
			if(data.equals(tmp.data)) {
				curNode.next = tmp.next;
				return true;
			}else {
				curNode = curNode.next;
				tmp = tmp.next;
			}
		}
		return false;
	}
	//打印数据
	public void printNode() {
		Node node = head;
		while(node != null) {
			System.out.println("元素:"+node.data);
			node = node.next;
		}
	}
	//计算长度
	public int length() {
		int len = 0;
		Node node = head;
		while(node != null) {
			len++;
			node = node.next;
		}
		return len;
	}
	public static void main(String[] args) {
		SingleLinklist sl = new SingleLinklist();
		sl.addNode(10);
		sl.addNode(7);
		sl.addNode(4);
		sl.addNode(5);
		System.out.println("生成的链表为:");
		sl.printNode();
		sl.deleteHead();
		System.out.println("删除头结点后链表为:");
		sl.printNode();
		sl.deleteNode(7);
		System.out.println("删除数据4后链表为:");
		sl.printNode();
		System.out.println("元素8是否存在?"+sl.findNode(8));
	}
}

上面的单向链表我将完整的代码贴出,之后的只需要稍加改动。
在这里插入图片描述

2.双端链表

对于单向链表,我们暂定是对头结点部位进行插入操作,但是这远远是不能满足我们的需求,如果想要在任意位置插入数据,或者直接在尾部插入,那么我们必须从头部一直遍历到尾部,这样的算法复杂度是O(n),如果我们适当的增加一个尾指针,用于指向尾结点,那么复杂度会直接降到O(1)。
在这里插入图片描述
双端链表的具体实现:

  //尾部添加节点
    	public void addTail(Object data) {
    		Node node = new Node(data);
    		if(tail == null) {
    			head = node;
    			tail = node;
    		}
    		tail.next = node;
    		tail = node;
    	}
基于双端链表实现队列:
    package com.lm.list1118;
/**
 * 双端链表实现队列
 * 先进先出(队首出,队尾进)
 * @author Administrator
 *
 */
public class QueueLinklist {
	private DoublePointLinklist dl;
	public QueueLinklist(){
		dl = new DoublePointLinklist();//创建双端链表对象
	}
	//入队列
	public void insert(Object data) {
		dl.addTail(data);
	}
	//出队列
	public void delete() {
		dl.deleteHead();
	}
	//获取队列长度
	public int length(){
		return dl.length();
	}
	//判断是否为空
	public boolean isEmpty() {
		if(dl.length()==0) 
			return true;
		return false;
	}
	//显示队列元素
	public void print() {
		dl.printNode();
	}
}

3.有序链表

一般说到链表,我们都认为他是无序的,这样就不方便查找最大值或最小值,所以可以对链表创建的时候进行排序,形成有序链表,这样可以通过O(1)的时间复杂度获取最值和删除最值(删除表头即可)。而且有序链表在插入的时候比有序数组速度更快,不需要进行元素的移动,而且不受固定大小的限制,所以有序链表有时候可以代替有序数组。
有序链表的具体实现:
插入函数:
在这里插入图片描述
在这里插入图片描述

4.有序链表优化无序数组的排序

我们知道冒泡排序,选择排序和插入排序需要的时间复杂度是O(N2)如果我们将无序的数组一个个取出然后插入有序链表中进行排序,然后再将排完的链表中的数据一个个取出重新放进数组,这个实现的排序。大概进行N2/4次比较,优化了效率。但是不足的地方就是开辟了需要的两倍的空间。

//插入结点,从小到大排列
     public void insert(int data) {
    	Node node = new Node(data);
    	Node pre = null;
    	Node cur = head;
    	}

我本来想着的是用两个变量,一个表示当前指针,一个表示当前指针的下一个指针,但是逻辑上容易出现空指针的情况,情况太多,考虑不全
但是如果将一个变量表示成当前指针的前一个就简单多了,因为如果当前指针不为空,那么前一个指针一定不为空

while(cur != null && cur.data<data) {
			pre = cur;
			cur = cur.next;
		}
		if(pre == null) {//当链表为空时
			head = node;
			head.next = cur;
		}else {//当链表不为空,并且找到插入的位置
			pre.next = node;
			node.next = cur;
		}
	}

5. 双向链表

所谓的双向链表,就是相对单向链表而言的,可以两个方向遍历。

在这里插入图片描述
借用网上的图来说明一下双向链表的插入和删除操作
在这里插入图片描述
具体的代码实现:

//表头增加节点
	public void addHead(Object data) {
		Node node = new Node(data);
		if(size==0) {
			head = node;
			tail = node;
		}else {
			head.prev = node;//不要忘记对prev指针的操作
			node.next = head;
			head = node;
		}
		size++;
	}
	//表尾增加节点
	public void addTail(Object data) {
		Node node = new Node(data);
		if(size == 0) {
			head = node;
			tail = node;
		}else {
			tail.prev = node;
			node.next = tail;
			tail = node;
		}
		size++;
	}
	//删除表头
	public void deleteHead() {
		if(size != 0) {
			head = head.next;
			head.prev = null;
			size--;
		}
	}
	//删除表尾
	public void deleteTail() {
		if(size!=0) {
			tail = tail.prev;
			tail.next = null;
			size--;
		}
	}

猜你喜欢

转载自blog.csdn.net/weixin_43732570/article/details/84249781
今日推荐