数据结构之链表
距离上次写数组的增删查改已经过去了很久了,我承认是我懒了,闲话不多说,进入今天的主题。
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干的节点(node)组成。
下面是链表和数组的特点的对比:
数组 | 链表 | |
访问方式 | 随机访问 | 顺序访问 |
存储方式 | 顺序存储 |
随机存储 |
适用场景 | 读多、写少 | 读少、写多 |
数组可以随机访问,链表只能按顺序访问。
数组在内存中的存储方式是顺序存储,链表则是随机存储。
数组适合读操作多,写操作少的场景。链表适合读操作少,写操作多的场景。
下面是节点的代码:
class Node{
int data;
Node next;
Node(int data){
this.data = data;
}
}
如果我们在Node的定义中再添加一个Node pre; 那么这个链表就是双向链表,即链表中的一个节点即知道它的上家也知道它的下家。
如果是双向链表,我们就可以从两头开始遍历,比如我们要找倒数第二个节点,就可以从尾部开始遍历,会快一些;
但是我们这里给出的是单向链表,即链表中的一个节点只知道它的下家。
不过这里都要除开头节点和尾节点,毕竟头节点没有上家,尾节点没有下家了。
下面给出了所有的代码。
package datastructure.linkedlist;
public class MyLinkedList{
//单向链表
//定义一个内部的Node节点以及构造方法
private static class Node{
int data;
Node next;
Node(int data){
this.data = data;
}
}
//头指针节点
private Node head;
//尾指针节点
private Node last;
//链表实际长度
private int size;
//下面依次是链表的查,改,增,删,打印操作
/**
* 链表的查询操作
* @param index 要找的元素的位置(顺序)
* @return 返回位置(顺序)为index的那个节点
* @throws Exception
*/
public Node get(int index) throws Exception{
//如果要查找的元素不在范围内,则抛出一个数组越界异常
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("超出链表的节点范围!");
}
//定义一个temp节点,从head头节点开始,然后遍历链表,直到找到那个元素
Node temp = head;
for(int i = 0; i < index; i++){
temp = temp.next;
}
//遍历完毕,返回现在的temp,就是要找的那个节点
return temp;
}
/**
* 链表的更改值操作
* @param index 要更改的值的位置(顺序)
* @param data 新值,用来替换旧值
* @throws Exception
*/
public void set(int index,int data) throws Exception{
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("超出链表的节点范围!");
}
get(index).data = data;
}
/**
* 链表的插入操作
* @param index 要插入的节点的位置
* @param data 要插入的节点的值
* @throws Exception
*/
public void insert(int index,int data) throws Exception{
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("超出链表的节点范围!");
}
//构造一个新的节点
Node insertNode = new Node(data);
if(size == 0){
//如果是空链表的话,就让头指针和尾指针指向插入的这个节点
head = insertNode;
last = insertNode;
}else if(index == 0){
//如果在头部插入
insertNode.next = head;
head = insertNode;
}else if(index == size){
//如果在尾部插入
last.next = insertNode;
last = insertNode;
}else{
//如果插入位置在中间
Node preNode = get(index - 1);
insertNode.next = preNode.next;
preNode.next = insertNode;
}
//修改链表的长度
size++;
}
/**
* 链表的删除操作
* @param index 要删除节点的位置(顺序)
* @return 要删除的节点
* @throws Exception
*/
public Node remove(int index) throws Exception{
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException("超出链表节点范围!");
}
Node removeNode = null;
if(index == 0){
//如果删除的是头节点
removeNode = head;
head = head.next;
}else if(index == size){
//如果删除的是尾节点
removeNode = last;
Node preNode = get(index - 1);
preNode.next = null;
last = preNode;
}else{
//如果删除的是中间元素
Node preNode = get(index - 1);
removeNode = preNode.next;
preNode.next = preNode.next.next;
}
size--;
return removeNode;
}
/**
* 链表的打印操作
*/
public void output(){
Node temp = head;
while(temp != null){
System.out.print(temp.data + "、");
temp = temp.next;
}
System.out.println();
}
/**
* 返回链表的长度
*/
public int getSize(){
return this.size;
}
public static void main(String[] args) throws Exception{
MyLinkedList mll = new MyLinkedList();
mll.insert(0, 3);
mll.insert(1, 19);
mll.insert(2, 79);
mll.insert(3, 44);
mll.insert(4, 77);
mll.output();
mll.set(0, 100);
mll.output();
mll.insert(2, 171);
System.out.println(mll.get(2).data);
mll.output();
mll.insert(0, 99);
mll.output();
mll.remove(3);
mll.output();
System.out.println(mll.getSize());
}
}
我们再来看看数组和链表的增删改查操作的时间复杂度。
增 | 删 | 查 | 改 | |
数组 | O(n) | O(n) | O(1) | O(1) |
链表 | O(1) | O(1) | O(n) | O(1) |
注意:这里链表的增删改都没有考虑之前的查找过程,只考虑纯粹的增删改操作。
今天的内容就到这里了。下期再见,谢谢大家的收看。