数据结构与算法:链表1

上次说到动态数组,

动态数组有个很明显的缺点,就是在增加元素、删除元素时候扩容很容易造成内存空间的浪费

链表是一种链式存储的线性表,所有的元素内存地址不一定连续。

链表的常见操作有:增加元素、删除元素等。

增加元素:

在增加元素的时候,主要是找到要插入位置的前一个节点。

删除元素:

同样和是找到要删除元素的上一个节点

链表类的设计:

class LinkedList<E>{//泛型
	private int size;//链表长度
	private Node<E> first;//指向第一个地址
	//内部类
	private static class Node<E>{
		E element;//存储的元素
		Node<E> next;//指向下一个地址
		public Node(E element, Node<E> next) {
			this.element = element;
			this.next = next;
		}
	}
	//链表的长度
	public int size() {
		return size;
	}
	//链表是否为空
	public boolean isEmpty() {
		return size == 0;
	}
	//清空链表
	public void clear() {
		size = 0;
		first = null;
	}
	//得到index位置上节点的值
	public E get(int index) {
		return node(index).element;
	}
	//修改index位置上节点的值
	public void set(int index, E element) {
		Node<E> node = node(index);
		node.element = element;
	}
	//在指定位置上添加元素(先获取前一个节点)
	public void add(int index, E element) {
		rangeCheckForAdd(index);
		if(index == 0) {
			//相当于newNode.next = first;  first = newNode
			first = new Node<>(element,first);
		}else {
			Node<E> preNode = node(index - 1);//上一个节点
			//相当于newNode.next = preNode.next;  preNode.next = newNode
			preNode.next = new Node<>(element,preNode.next);
		}
		size++;
	}
	//往链表的最后添加元素
	public void add(E element) {
		add(size,element);
	}
	//删除节点(先获取前一个节点)
	public void remove(int index) {
		if(index == 0) {
			first = first.next;
		}else {
			Node<E> preNode = node(index - 1);
			preNode.next = preNode.next.next;
		}
		size--;
	}
	//找到index位置上的节点
	private Node<E> node(int index){
		rangeCheck(index);//边界检查
		Node<E> node = first;
		for (int i = 0; i < index; i++) {
			node = node.next;
		}
		return node;
	}
	//是否包含某个元素
	public boolean contains(E element) {
		return indexOf(element) != -1;
	}
	public int indexOf(E element) {
		//元素可以是null时
		if(element == null) {
			Node<E> node = first;
			for (int i = 0; i < size; i++) {
				if(node.element == element) return i;
				node = node.next;
			}
		}else {
			Node<E> node = first;
			for (int i = 0; i < size; i++) {
				if(element.equals(node.element)) return i;
				node = node.next;
			}
		}
		return -1;//没有找到
	}
	//index越界的时候,抛出异常
	private void outOfBounds(int index) {
		throw new IndexOutOfBoundsException("Index: "+index+",Size :"+size);
	}
	//对边界的检查
	private void rangeCheck(int index) {
		if(index < 0 || index >= size) {
			outOfBounds(index);
		}
	}
	//添加元素时,对边界的检查
	private void rangeCheckForAdd(int index) {
		if(index < 0 || index > size) {
			outOfBounds(index);
		}
	}
	//打印链表
	@Override
	public String toString() {
		StringBuilder string = new StringBuilder();
		string.append("size = ").append(size).append(",[");
		Node<E> node = first;
		for (int i = 0; i < size; i++) {
			if(i!=0) string.append(", ");
			string.append(node.element);
			node = node.next;
		}
		string.append("]");
		return string.toString();
	}
}

练习题:

1.删除链表的结点

.

 注意:这道题只给你了要删除的节点,我们找不到它的前一个节点,这时我们可以把它的下一个节点的值赋给它,删除它的下一个节点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

2.反转链表

leetcode:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/

这道题有两种方法:迭代法和递归法

迭代法:遍历原来的链表,然后用头插法一次插入新的链表。

           

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode newhead = null;
        while(head!=null){
            ListNode temp = head.next;//先用保存要插入节点的下一个节点,避免链表断开
            head.next = newhead;
            newhead = head;
            head = temp;
        }
    }
}

递归法:

这个稍微有点抽象,首先要搞清楚函数的功能,它返回的是已经反转好的newhead

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode newhead = ListNode reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newhead;
    }
}

3.环形链表

leetcode:https://leetcode-cn.com/problems/linked-list-cycle/

使用了一种叫快慢指针的思想,定义一个走的慢的slow指针,定义一个走的快的fast指针,如果链表中存在环形,fast指针最终会和slow指针指向同一地址,就像跑操场一样,跑得快的人,过一段时间后,会从后面追上跑得慢的人。如果fast最终出去了链表,那么就不存在环。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null) return false;
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null && fast.next != null) {
        	slow = slow.next;
        	fast = fast.next.next;
            if(slow == fast) return true;
        }
        return false;
    }
}

补充: 

对于这个快慢指针法,还可用于这道题:https://leetcode-cn.com/problems/middle-of-the-linked-list/

用两个指针 slow与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast!=null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值