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--;
}
}