数据结构与算法-链表

链表

链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的“结点”。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。

链表也是线性表;

特点

  1. 不需要连续的内存空间。
  2. 有指针引用
  3. 三种最常见的链表结构:单链表、双向链表和循环链表

单链表

在这里插入图片描述
从单链表图中,可以发现,有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。我们一般把第一个结点叫作头结点,把最后一个结点叫作尾结点。
其中,头结点用来记录链表的基地址。通过他可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。
while(p.next != null){} 通常用来当作链表是否为最后一个结点。

LinkedList与ArrayList

从链表的查找、插入和删除进行比较。
数据结构分析、ArrayList底层是数组、而LinkedList底层是Node结点,用指针相连。

  • 查找: 由于ArrayList底层数组的随机访问特性、定位元素的速度远比LinkedList快得多,因为LinkedList每次都需要从头结点遍历找到合适的结点。
  • 插入、删除:ArrayList底层数组插入、删除元素会经常涉及到移位操作,而LinkedList只需要改变指针指向就可以完成插入删除操作。因为,插入、删除上LinkedList比ArrayList更高效;

数组VS链表

重要区别:

  1. 数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高
  2. 链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。
  3. 数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out ofmemory)”。如果声明的数组过小,则可能出现不够用的情况。
  4. 动态扩容:数组需再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容。
    在这里插入图片描述

循环链表

循环链表是一种特殊的单链表。实际上,循环链表也很简单。它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。从我画的循环链表图中,你应该可以看出来,它像一个环一样首尾相连,所以叫作“循环”链表。
在这里插入图片描述

双向链表

  • 双向链表,顾名思义,它支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。
  • 双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。
  • 虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。

单项链表实现

package algorithm.list;

public class MyLinkedList {
	
	private ListNode head;
	private int size = 0;		//
	
	public void insertHead(int data){		//插入链表的头部		data就是插入的数据
		ListNode newNode = new ListNode(data);
		//如果原来就有数据呢?
		newNode.next = head;		//栈内存的引用
		head = newNode;
		
		//插入O(1)
	}
	
	public void insertNth(int data,int position){		//插入链表的中间 假设定义在第N个插入 O(n)
		if(position == 0) {		//这个表示插入在头部了
			insertHead(data);
		}else{
			ListNode cur = head;
			for(int i = 1; i < position ; i++){
				cur = cur.next;		//一直往后遍历   p=p->next;  ->是c++里面的往后找指针
			}
			ListNode newNode = new ListNode(data);
			//
			newNode.next = cur.next;		//新加的点指向后面 保证不断链
			cur.next = newNode;			//把当前的点指向新加的点
		}
	}
	/*int a = 1;
	int b = a;
	int a = 2;*/
	
	public void deleteHead(){//O(1)
		head = head.next;
	}
	
	public void deleteNth(int position){//O(n)
		if(position == 0) {
			deleteHead();
		}else{
			ListNode cur = head;
			for(int i = 1; i < position ; i ++){
				cur = cur.next;
			}
			cur.next = cur.next.next; //cur.next 表示的是删除的点,后一个next就是我们要指向的
		}
	}
	
	public void find(int data){//O(n)
		ListNode cur = head;
		while(cur != null){
			if(cur.value == data) break;
			cur = cur.next;
		}
	}
	
	public void print(){
		ListNode cur = head;
		while(cur != null){
			System.out.print(cur.value + " ");
			cur = cur.next;
		}
		System.out.println();
	}
	public static void main(String[] args) {
		MyLinkedList myList = new MyLinkedList();
		myList.insertHead(5);
		myList.insertHead(7);
		myList.insertHead(10);
		myList.print(); // 10 -> 7 -> 5
		myList.deleteNth(0);
		myList.print(); // 7 -> 5
		myList.deleteHead();
		myList.print(); // 5
		myList.insertNth(11, 1);
		myList.print(); // 5 -> 11
		myList.deleteNth(1);
		myList.print(); // 5
	}
}

class ListNode{
	
	int value;		//值
	ListNode next;	//下一个的指针

	ListNode(int value){
		this.value = value;
		this.next = null;
	}
}

双向链表实现

package algorithm.list;

public class DoubleLinkList {		// 双向链表

	private DNode head;		//头 
	private DNode tail;		// 尾
	
	DoubleLinkList(){
		head = null;
		tail = null;
	}
	
	public void inserHead(int data){
		DNode newNode = new DNode(data);
		if(head == null){
			tail = newNode;
		}else{
			head.pre = newNode;
			newNode.next = head;
		}
		head = newNode;
	}
	public void deleteHead(){
		if(head == null) return ;		//没有数据
		if(head.next == null){		//就一个点
			tail = null;
		}else{
			head.next.pre = null;	
		}
		head = head.next;
	}
	public void deleteKey(int data){
		DNode current = head;
		while (current.value != data) {
			if (current.next == null) {
				System.out.println("没找到节点");
				return ;
			}
			current = current.next;
		}
		if (current == head) {// 指向下个就表示删除第一个
			deleteHead();
		} else {
			current.pre.next = current.next;
			if(current == tail){		//删除的是尾部
				tail = current.pre;
				current.pre = null;
			}else{
				current.next.pre = current.pre;
			}
		}
	}
}

class DNode{
	
	int value;		//值
	DNode next;		//下一个的指针
	DNode pre;		//指向的是前一个指针

	DNode(int value){
		this.value = value;
		this.next = null;
		this.pre = null;
	}
}

重要是链表的思想,而不是代码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值