大家好,本系列开始讲结合刷力扣的过程,对数据结构和算法进行总结。牧码心今天给大家推荐一篇数据结构与算法系列(一)—链表的文章,希望对你有所帮助。大纲如下:
链表是线性表的一种,相比于数组,链表是一种稍微复杂一点的数据结构,为了更好的理解链表和数组,我们先来看下这两者的区别:
时间复杂度 | 链表 | 数组 |
---|---|---|
插入/删除 | O(1) | O(n) |
随机访问 | O(n) | O(1) |
综上分析,链表是通过指针将一组零散的内存块串联在一起,其中内存块也可以成为结点,一般结点由数据域(data)和指针域(next)构成,指针(next)用于指向其他结点的地址。
链表按照结构可以分为单链表,双向链表和循环链表等类型的链式结构。每种类型的链表各自有基本操作特点,下面我们分别介绍:
说明:在"节点10"与"节点20"之间添加"节点15"
删除"节点30",删除之后,“节点20” 的后继节点为"节点40"。
说明:在"节点10"与"节点20"之间添加"节点15"
说明:删除"节点30"
/**
* @program:com.greekw.datastruct.linkedlist
* @desc:单链表操作demo
* 1、单链表的插入,删除,查找
* @Author:greekw
* @Date:2020-06-27 18:49
*/
public class SinglyLinkedListDemo {
// 定义一个头结点
private static Node head=null;
// 定义一个Node类封装结点数据
public static class Node{
private int data;
private Node next;
public Node(int data,Node next){
this.next=next;
this.data=data;
}
public int getData(){
return data;
}
}
public static Node createNode(int data){
return new Node(data,null);
}
// 创建链表,头插入,头插法建立链表,输入顺序是相反的,即逆序
public static void addAtHead(int data){
Node new_node=new Node(data,null);
if(head==null){
head=new_node;
}else {
new_node.next=head;
head=new_node;
}
}
// 创建链表,尾插法,在链表尾部顺序插入
public static void addAtTail(int data){
Node new_node=new Node(data,null);
// 空链表,则可以赋值给head
if(head==null){
head=new_node;
}else {
Node p=head;
//寻找尾结点,尾指针不是指向下一个结点,而是指向一个空地址 NULL
while (p.next!=null){
p=p.next;
}
new_node.next=p.next;
p.next=new_node;
}
}
// 插入在指定结点前
public static void addAtBefore(Node p,int data){
Node new_node=new Node(data,null);
if (p == null) return;
if(p==head){
addAtHead(data);
return;
}
Node q=head;
while (q!=null && q.next.data!=p.data){
q=q.next;
}
if(q==null) return ;
new_node.next=q.next;
q.next=new_node;
}
// 插入在指定结点后
public static void addAtAfter(Node p,int data){
Node new_node=new Node(data,null);
if (p == null) return;
Node q=head;
while (q!=null && q.data!=p.data){
q=q.next;
}
if(q==null) return ;
new_node.next=q.next;
q.next=new_node;
}
// 遍历链表
public static void printAt(){
Node p=head;
while (p!=null){
System.out.println(p.data);
p=p.next;
}
}
// 链表查找,根据索引查找
public static Node findByIndex(int index){
Node p=head;
int pos=0;
// 遍历链表,查找与index相同的位置
while (p!=null && pos!=index){
p=p.next;
++pos;
}
return p;
}
// 链表查找,根据值查找
public static Node findByValue(int data){
Node p=head;
while (p!=null && p.data!=data){
p=p.next;
}
return p;
}
// 链表删除节点
public static void deleteNode(Node p){
if(p==null || head==null) return;
// 删除头结点
if(head==p){
head=head.next;
return;
}
// 查找要删除的节点
Node q=head;
while (q!=null && q.next.data!=p.data){
q=q.next;
}
if(q==null) return;
q.next=q.next.next;
}
// 测试用例
public static void main(String[] args) {
for(int i=0;i<20;i++){
addAtTail(i*2);
}
//printAt();
/*Node node=findByIndex(3);
System.out.println(node.data);
Node node1=findByValue(10);
System.out.println(node1.data);*/
addAtAfter(createNode(2),7);
printAt();
addAtBefore(createNode(2),11);
printAt();
deleteNode(createNode(2));
printAt();
}
}
/**
* @program:com.greekw.datastruct.linkedlist
* @desc:双链表操作Demo
* jdk基于双链表实现的可参考 {@link LinkedList}
* @Author:greekw
* @Date:2020-07-05 17:19
*/
public class DoubleLinkedListDemo<T>{
// 表头
private DNode<T> mHead;
// 节点个数
private int mCount;
// 双向链表“节点”对应的结构体
private class DNode<T> {
public DNode prev;
public DNode next;
public T value;
public DNode(T value, DNode prev, DNode next) {
this.value = value;
this.prev = prev;
this.next = next;
}
}
// 构造函数
public DoubleLinkedListDemo() {
// 创建“表头”。注意:表头没有存储数据!
mHead = new DNode<T>(null, null, null);
mHead.prev = mHead.next = mHead;
// 初始化“节点个数”为0
mCount = 0;
}
// 返回节点数目
public int size() {
return mCount;
}
// 返回链表是否为空
public boolean isEmpty() {
return mCount==0;
}
// 获取第index位置的节点
private DNode<T> getNode(int index) {
if (index<0 || index>=mCount)
throw new IndexOutOfBoundsException();
// 正向查找
if (index <= mCount/2) {
DNode<T> node = mHead.next;
for (int i=0; i<index; i++)
node = node.next;
return node;
}
// 反向查找
DNode<T> rnode = mHead.prev;
int rindex = mCount - index -1;
for (int j=0; j<rindex; j++)
rnode = rnode.prev;
return rnode;
}
// 获取第index位置的节点的值
public T get(int index) {
return getNode(index).value;
}
// 获取第1个节点的值
public T getFirst() {
return getNode(0).value;
}
// 获取最后一个节点的值
public T getLast() {
return getNode(mCount-1).value;
}
// 将节点插入到第index位置之前
public void insert(int index, T t) {
if (index==0) {
DNode<T> node = new DNode<T>(t, mHead, mHead.next);
mHead.next.prev = node;
mHead.next = node;
mCount++;
return ;
}
DNode<T> inode = getNode(index);
DNode<T> tnode = new DNode<T>(t, inode.prev, inode);
inode.prev.next = tnode;
inode.next = tnode;
mCount++;
return ;
}
// 将节点插入第一个节点处。
public void insertFirst(T t) {
insert(0, t);
}
// 将节点追加到链表的末尾
public void appendLast(T t) {
DNode<T> node = new DNode<T>(t, mHead.prev, mHead);
mHead.prev.next = node;
mHead.prev = node;
mCount++;
}
// 删除index位置的节点
public void del(int index) {
DNode<T> inode = getNode(index);
inode.prev.next = inode.next;
inode.next.prev = inode.prev;
inode = null;
mCount--;
}
// 删除第一个节点
public void deleteFirst() {
del(0);
}
// 删除最后一个节点
public void deleteLast() {
del(mCount-1);
}
// 测试用例
public static void main(String[] args) {
int[] iarr = {10, 20, 30, 40};
System.out.println("\n----int_test----");
// 创建双向链表
DoubleLinkedListDemo<Integer> dlink = new DoubleLinkedListDemo<Integer>();
dlink.insert(0, 20); // 将 20 插入到第一个位置
dlink.appendLast(10); // 将 10 追加到链表末尾
dlink.insertFirst(30); // 将 30 插入到第一个位置
// 双向链表是否为空
System.out.printf("isEmpty()=%b\n", dlink.isEmpty());
// 双向链表的大小
System.out.printf("size()=%d\n", dlink.size());
// 打印出全部的节点
for (int i=0; i<dlink.size(); i++)
System.out.println("dlink("+i+")="+ dlink.get(i));
}
}
总之,链表和数组的对比,在实际的软件开发中,不能仅仅利用复杂度分析就决定使用哪个数据结构来存储数据。要根据具体情况,权衡究竟是选择数组还是链表