首页 > 技术文章 > 数据结构与算法(5)-链表,双端链表,有序链表,双向链表,迭代器

guan-li 2018-11-06 16:43 原文


1.普通链表

链表结构的每个节点汇包含一个数据域和一个引用域,引用域直接存放下一个节点的地址.

如图所示:


  • 代码示例
//链节点
public class Link {
    private Link next;  //下个节点
    private String data;    //数据
    public Link(String data){
        this.data = data;
    }

    public Link getNext() {
        return next;
    }
    public void setNext(Link next) {
        this.next = next;
    }
    
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
}
//链表 
//链表也可以用first节点来表示
public class LinkList {
    private Link first;
    public LinkList() {
        first = null;
    }

    //打印链表
    public void display() {
        if (first == null) {
            System.out.println("LinkList is null");
            return;
        }

        Link temp = first;
        while (temp != null) {
            System.out.print(temp.getData() + "-->");
            temp = temp.getNext();
        }
        System.out.println();
    }

    //插入节点
    public void insert(Link link) {
        link.setNext(first);
        first = link;
    }

    //删除节点
    public void delete(String data){
        Link pre = null;
        Link temp = first;

        while(temp != null){
            if(data.equals(temp.getData())){
                if(pre != null){
                    pre.setNext(temp.getNext());
                }
                //如果删除的是第一个节点,移动first
                if(temp.equals(first)){
                    first = temp.getNext();
                }
                break;
            }
            pre = temp;
            temp = temp.getNext();
        }
        this.display();
    }

    //查询节点
    public Link query(String data) {
        Link temp = first;
        Link result = null;
        while (temp != null) {
            if (data.equals(temp.getData())) {
                result = temp;
                break;
            }
            temp = temp.getNext();
        }
        return result;
    }

    public static void main(String[] args) {
        LinkList linkList = new LinkList();

        //插入节点
        linkList.insert(new Link("张三"));
        linkList.insert(new Link("李四"));
        linkList.insert(new Link("王五"));
        linkList.insert(new Link("王五"));

        //展示节点
        linkList.display();

        //查找节点
        Link link = linkList.query("李四");
        System.out.println(link.getData());

        //删除节点
        linkList.delete("张三");
    }
}

输出:

王五-->王五-->李四-->张三-->
李四
王五-->王五-->李四-->

2.双端链表

first和last初始都为空,两端都可以插入,只有一个节点时,既是first也是last.

如图所示:


  • 代码示例
//双端链表,头部尾部都可以插入删除
public class FirstLastLinkList {
    private Link first;
    private Link last;

    public FirstLastLinkList() {
        this.first = null;
        this.last = null;
    }


    public Link getFirst() {
        return first;
    }

    public void setFirst(Link first) {
        this.first = first;
    }

    public Link getLast() {
        return last;
    }

    public void setLast(Link last) {
        this.last = last;
    }

    //打印链表
    public void display() {
        Link temp = first;
        while (temp != null) {
            System.out.print(temp.getData() + "-->");
            temp = temp.getNext();
        }
        System.out.println();
    }

    //判空
    public boolean isEmpty() {
        return first == null;
    }


    //头部插入
    public void insertFirst(Link link) {
        link.setNext(first);
        first = link;
        //如果下个节点为空,则为尾部节点
        if (link.getNext() == null) {
            last = link;
        }
    }

    //尾部删除
    public void deleteLast() {
        if (isEmpty()) {
            return;
        }
        Link temp = first;
        while (temp != null) {
            if (temp.equals(last)) {
                first = null;
                last = null;
                return;
            }
            if (temp.getNext().equals(last)) {
                last = temp;
                last.setNext(null);
                return;
            }
            temp = temp.getNext();
        }
    }

    public static void main(String[] args) {
        FirstLastLinkList firstLastLinkList = new FirstLastLinkList();

        //头部插入
        firstLastLinkList.insertFirst(new Link("张三"));
        firstLastLinkList.insertFirst(new Link("张三"));
        firstLastLinkList.insertFirst(new Link("李四"));
        firstLastLinkList.insertFirst(new Link("王五"));
        firstLastLinkList.display();

        //尾部删除
        firstLastLinkList.deleteLast();
        firstLastLinkList.display();

    }
}

输出:

王五-->李四-->张三-->张三-->
王五-->李四-->张三-->

  • 链表的效率

插入和删除 O(1)
查找O(N)

  • 链表的优点

和数组相比,删除操作不需要节点的移动,效率有一定的提升.
另一个优点,链表的内存利用率高,有几个节点就占用多少内存,不需要指定节点数据.而数组需要预先开辟出一定长度的内存.虽然数组支持动态扩展,仍然是按比例的,比如长度为10扩展到20.

  • 链表的缺点

链表相对数组的缺点:随机查找不方便.数组可以根据角标随机取出一个位置的数.而链表无法这么做,只能从头开始遍历.

  • 链表的用途

普通链表实现栈,从first插入,从first取出即可,且理论上长度不限.
双端链表实现队列,从last插入,从first取出.很好地避免了用数组实现时的指针回绕问题.
有序链表可以用于实现优先级队列.


3.有序链表

节点在链表中依大小有序排列.


  • 有序链表的优缺点

有序列表相对于有序数组的优点:
插入,删除效率较高,不用移动节点
内存利用率高.

  • 有序链表的效率

删除O(1) 插入O(N)

  • 有序链表的用途

有序链表可以用于实现优先级队列
有序链表可以用于数组的排序: 以插入排序为例,时间复杂度为O(N的平方).


  • 代码示例
//链节点
public class Link {
	private int data;
	public Link next;
	
	public Link(int data){
		this.data = data;
	}
	
	public int getData(){
		return this.data;
	}
	
	public void display(){
		System.out.println("节点是"+data+",它的下一个节点是"+(null==next?"null":this.next.getData()));
	}
}
//有序链表
public class SortedLinkList {
	private Link first;
	
	public SortedLinkList(){
		this.first = null;
	}
	
	//插入时进行排序
	public void insert(Link link){
		Link pre = null;
		Link cur = first;
		
		while(cur!=null && link.getData() <= cur.getData()){
			pre = cur;
			cur = cur.next;
		}
		
		if(null == pre){	//如果cur没有移动
			first = link;
		}else{
			pre.next = link;
		}
		link.next = cur;
		System.out.println("插入节点:"+link.getData());
	}
	
	//删除一般只删除表头节点
	public Link remove(){
		Link temp = first;
		if(null!=first){
			first = first.next;
		}
		
		System.out.println("取出节点:"+(null==temp?"null":temp.getData()));
		return temp;
	}
	
	public static void main(String[] args) throws Exception{
		SortedLinkList list = new SortedLinkList();
		list.insert(new Link(20));
		list.insert(new Link(70));
		list.insert(new Link(60));
		
		list.remove();
		list.remove();
		list.remove();
	}
}

输出:

插入节点:20
插入节点:70
插入节点:60
取出节点:70
取出节点:60
取出节点:20

4.双向链表

双向链表节点中有两个引用域,分别指向上一个节点和下一个节点,双向链表也可以是双端链表.

如图所示:


  • 双向链表的应用

如文本编辑器中,光标不仅可以向前移动还可以向后移动.


  • 代码示例
//链表
public class Link {
	private int data;	
	public Link next;
	public Link pre;
	
	public Link(int data){
		this.data = data;
	}
	
	public int getData(){
		return this.data;
	}
}

5.迭代器

将集合的和遍历相关操作委托给迭代器,通过迭代器中指针的移动进行相关操作.


  • 代码示例
public class Link {
	private int data;
	public Link next;
	
	public Link(int data){
		this.data = data;
	}
	
	public void display(){
		System.out.print(data+">");
	}
}
public class LinkList {
	private Link first;
	
	public LinkList(){
		first = null;
	}
	
	//获取第一个节点
	public Link getFirst(){
		return first;
	}
	
	//设置第一个节点
	public void setFirst(Link f){
		first = f;
	}
	
	//判空
	public boolean isEmpty(){
		return null == first;
	}
	
	//获取迭代器
	public ListIterator getIterator(){
		return new ListIterator(this);
	}
	
	//展示list
	public void displayList(){
		Link cur = first;
		while(null != cur){
			cur.display();
			cur = cur.next;
		}
		System.out.println();
	}
}
//迭代器的主要作用是将集合的一些遍历操作委托给迭代器
public class ListIterator {
	private Link cur;
	private Link pre;
	private LinkList list;
	
	public ListIterator(LinkList list){
		this.list = list;
		reset();
	}
	
	//重置迭代器
	public void reset(){
		cur = list.getFirst();
		pre = null;
	}
	
	//判断是否在末尾
	public boolean atEnd(){
		return null == cur.next;
	}
	
	//指针移动到下一个
	public void goNext(){
		pre = cur;
		cur = cur.next;
	}
	
	//获取当前指针
	public Link getCur(){
		return cur;
	}
	
	//在当前指针后插入
	public void insertAfterCur(Link newLink){
		//如果list为空
		if(list.isEmpty()){
			list.setFirst(newLink);
			cur = newLink;
		}else{
			newLink.next = cur.next;
			cur.next = newLink;
			goNext();
		}
	}
	
	//在当前指针前插入
	public void insertBeforeCur(Link newLink){
		if(null == pre){	//指针在链头
			pre.next = list.getFirst();
			list.setFirst(newLink);
			reset();
		}else{
			newLink.next = pre.next;
			pre.next = newLink;
			cur = newLink;
		}
	}
	
	//获取当前指针
	public Link deleteCur(){
		Link temp = cur;
		if(list.isEmpty()){
			System.out.println("链表为空!!!");
			return null;
		}
		if(null == pre){//指针在链头
			list.setFirst(cur.next);
			reset();
		}else{
			pre.next = cur.next;
			if(atEnd()){	//指针在链尾,直接移到链头
				reset();
			}else{
				cur = cur.next;
			}
		}
		
		return temp;
	}
	
	public static void main(String[] args) {
		LinkList list = new LinkList();
		ListIterator iterator = list.getIterator();
		
		iterator.insertAfterCur(new Link(10));
		iterator.insertAfterCur(new Link(20));
		iterator.insertBeforeCur(new Link(30));
		iterator.insertBeforeCur(new Link(40));
		list.displayList();
		iterator.deleteCur();
		list.displayList();
	}
}

输出:

10>40>30>20>
10>30>20>


6.扩展-合并k个有序链表

/**
 * 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
 * 示例:
 * 输入:
 * [
 *   1->4->5,
 *   1->3->4,
 *   2->6
 * ]
 * 输出: 1->1->2->3->4->4->5->6
 */
public class MergeKListsSolution {
    //方法一:递归合并两个有序链表,k个采用两两合并的方法
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        //该递归的边界,为l1或l2有一个为空
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l2.next, l1);
            return l2;
        }
    }

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }
        if (lists.length == 1) {
            return lists[0];
        }

        ListNode temp = lists[0];
        for (int i = 1; i < lists.length; i++) {
            temp = this.mergeTwoLists(temp, lists[i]);
        }

        return temp;
    }

    //方法二:先从多个链头找出最小值,递归
    public ListNode mergeKLists2(ListNode[] lists) {
        //先确定递归的收敛边界,lists中不为空的元素个数为0或1
        ListNode min = null;
        int index = -1;
        for (int i = 0; i < lists.length; i++) {
            if (lists[i] == null) {
                continue;
            }
            if (min == null || lists[i].val < min.val) {
                min = lists[i];
                index = i;
            }
        }
        if (index != -1) {
            lists[index] = lists[index].next;
            min.next = mergeKLists2(lists);
        }
        return min;
    }
}

推荐阅读