数据结构——单向链表

单向链表

链表通常是指单向链表,它包含多个结点,每个结点有一个指向后继元素的指针。表中最后一个结点的next指针的值为NULL,表示该链表的结束。结构如下图所示:


下面的代码时一个链表的类型声明:

public class ListNode {
  private int data;
  private ListNode next;
  public ListnNode(int data){
    this.data = data;
  }
  //getter and setter ......
}

链表的基本操作

  • 遍历链表
  • 插入元素
  • 删除元素

链表的遍历

遍历链表需要完成以下步骤。

  • 沿指针遍历
  • 遍历时显示结点的内容
  • 当next指针的值为NULL时,结束遍历
    获取链表长度和遍历链表的伪代码如下:
  /**
  * 获取链表长度
  */
  int ListLength(ListNode headNode){
    int length = 0;
    ListNode currentNode = headNode;
    while(currentNode != null){
      length++;
      currentNode = currentNode.getNext();
    }
    return length;
  }

  /**
  * 遍历链表
  */
  void printListNode(ListNode headNode){
    ListNode currentNode = headNode;
    while(currentNode != null){
       print(currentNode.getData());
        currentNode = currentNode.getNext();
    }
  }

在单向链表的开头插入结点

如果要在当前表头插入一个新的结点,只需要修改一个next指针__(新结点的next指针)__,可以通过以下俩个步骤来完成:

  • 更新新结点next指针,使其指向当前表头的结点。

  • 更新表头指针的值,使其指向新结点。

在单向链表的结尾插入结点

如果需要在表尾插入新结点,则需要修改俩个next指针(最后一个next指针和新结点的next指针)。

  • 新节点的next指针指向NULL。

  • 最后一个结点的next指针指向新结点。

在单向链表的中间插入结点

如果给定插入的位置,在这种情况下,需要修改俩个指针(指定位置前一个结点的next指针和新结点的next指针)。

  • 首先找到要插入位置的前一个结点,例如要在2位置插入,那么应该先找到1位置的结点,新结点的next指针应该改指向1位置结点的下一个结点。
  • 然后将1位置的next指针指向新结点。

上述三种插入方式的伪代码如下:

  ListNode InsertInLinkedList(ListNode headNode, ListNode nodeToInsert, int position){
    if(headNode == null){  //若链表为空,插入
      return nodeToInsert;
    }
    int size = ListLenth(headNode);  //获取链表长度
    if(position > size+1 || position < 1){  //如果给定位置超出链表大小或者小于1则打印警告
       print("给定位置非法,位置的有效值为1~"+size+1);
       return headNode;
    }
    if(position == 1){  //在表头插入
      nodeToInsert.setNext(headNode);
      return nodeToInsert;
    }else{
      //在链表中间或末尾插入
      ListNode previousNode = headNode;
      int count = 1;
      while(count < position-1){
        previousNode = previousNode.getNext();
        count++;
      }
      ListNode currentNode = previousNode.getNext();
      nodeToInsert.setNext(currentNode);
      previousNode.setNext(nodeToInsert);
    }
    return headNode;
  }

上述的几种插入方式可以分开单独的方法去实现。文章结尾有关于本文提及到的所有方法的demo,可供参考。

单向链表的删除

单向链表的删除也有三种情况:

  • 删除链表的表头
  • 删除链表的表尾
  • 删除链表中间的结点

删除单向链表的第一个结点

删除单向链表的第一个结点可以分为两步实现:

  • 创建一个临时结点,它指向表头指针所指的结点。
  • 修改表头指针所指的值,使其指向下一个结点,并移除临时结点。
    总体思想就是将指向表头的指针移动到表头指向的结点,然后删除表头
  ListNode currentNode = headNode.getNext();
  headNode = null;
  return currentNode;

删除单向链表最后一个结点

这种情况下将删除链表的最后一个结点。需要三个步骤来实现:

  • 遍历链表,在遍历时要保存前驱结点(前一次经历过的结点)的位置。当遍历到链表的表尾时,将有俩个指针——表尾(tail)的指针和前驱结点的指针.

  • 将前驱结点的next指针更新为NULL。

  • 移除表尾结点。

删除单向链表中间的结点

在这种情况下,被删除的结点总是位于俩个结点之间,因此,不需要更新表头和表尾的指针,该删除操作可通过两步实现:

  • 遍历链表,遍历时保存前驱结点的位置,当找到要删除的结点,将前驱结点next指针更新为被删除结点的next指针的值。

  • 移除需要删除的当前结点

  ListNode DeleteNodeFromLinkedList(ListNode headNode, int position){
    int size = getLinkedListLength(headNode);
    if(position > size || position < 1){
      print("删除位置非法,位置应该介于1~"+size+"之间");   
    }
    if(position == 1){  //删除单向链表的表头结点
      ListNode currentNode = headNode.getNext();
      headNode = null;
    }else{  //删除中间或表尾结点
      ListNode previousNode = headNode;
      int count = 1;
      while(count < position){
        previousNode = previousNode.getNext();
        count++;
      }
      ListNode currentNode = previousNode.getNext();
      previousNode.setNext(currentNode.getNext());
      currentNode = null;
    }
    return headNode;
  }

上面的三种删除方式也可以单独使用三个方法去去实现,实现方式在文章结尾的完整demo中。

上面就是对单向链表的简单的介绍,在文章最后通过一个demon将上述的操作整体的练习一遍。代码分为三个文件——ListNode.java、SingleList.java、Test.java。

代码DEMON部分

ListNode.java

  package bokeyuan.linked;
  
  /**
  * 链表结构
  */
  public class ListNode {
	
    private Object data;
	
    private ListNode next;
	
    public ListNode(Object data) {
	this.data = data;
    }

    public Object getData() {
	return data;
    }

    public void setData(Object data) {
	this.data = data;
    }

    public ListNode getNext() {
	return next;
    }

    public void setNext(ListNode next) {
	 this.next = next;
    }
  }

SingleList.java

package bokeyuan.linked;

public class SingleList {

	private ListNode headNode;
	
	/*------------------------------------------------单向链表三种插入方法-------------------------------------------*/

	/**
	 * 单向链表插入——从头部插入
	 * 
	 * @param data 新结点数据
	 */
	public void addFromHead(Object data) {
		ListNode newNode = new ListNode(data); // 创建一个新的结点
		ListNode currentNode = headNode; // 保存道歉head结点的值
		headNode = newNode; // 将新结点作为头部结点
		if (currentNode != null)
			newNode.setNext(currentNode); // 将新头部结点的next指针指向上面保存的旧的head结点值
	}

	/**
	 * 单向链表插入——从表尾插入
	 * 
	 * @param data 新结点数据
	 */
	public void addFromTail(Object data) {
		ListNode newNode = new ListNode(data);
		if (headNode == null) {	//如果headNode为空,则直接将新结点赋给头部
			headNode = newNode;
			return;
		}
		ListNode tempNode = headNode;	//创建临时结点
		while(tempNode.getNext() != null) {	//遍历结点,找到尾部结点后将它的next指针更新为新结点
			tempNode = tempNode.getNext();
		}
		tempNode.setNext(newNode);

	}
	
	
	/**
	 * 指定位置插入
	 * @param data 将要插入的数据
	 * @param position 指定的位置
	 */
	public void add(Object data, int position) {
		ListNode newNode = new ListNode(data);
		if(headNode == null) {
			headNode = newNode;
			return;
		}
		int size = listLength();	//获取链表的长度
		if(position > size || position < 0) {
			System.err.println("指定位置不合法,请给定0~"+(size-1)+"的值");
		}
		if(position == 0) {	//如果指定了0则调用从头部插入方法
			addFromHead(data);
			return;
		}
		ListNode previousNode = headNode;	//前驱结点
		int count = 1;		//注意这里应该从1开始,因为0的时候走的是上面第一个if语句,如果这里count从0开始的画会报空指针错误
		while(position != count) {
			count++;
			previousNode = previousNode.getNext();
		}
		ListNode currentNode = previousNode.getNext();	//获取到前驱结点的next指针指向的结点
		newNode.setNext(currentNode); 	//新结点的next指针指向前驱结点next指针指向的值
		previousNode.setNext(newNode); 	//将前驱结点的next指针更新为新结点
	}
	
	
	/*------------------------------------------------单向链表三种删除方法-------------------------------------------*/
	
	/**
	 * 从头部删除链表元素
	 */
	public void removeFromHead() {
		if(headNode != null) {
			ListNode tempNode = headNode;	//创建临时结点指向头部结点
			headNode = tempNode.getNext();	//修改头部的值为上一步中的临时结点
		}
	}
	
	/**
	 * 从尾部删除链表元素
	 */
	public void removeFromTail() {
		if(headNode.getNext() == null) {	//如果当前只有一个结点
			headNode = null;
		}
		if(headNode != null && headNode.getNext() != null) {
			ListNode previousNode = headNode;	//前驱结点
			while(previousNode.getNext().getNext() != null) {	//遍历寻找表尾的前驱结点
				previousNode = previousNode.getNext();
			}
			previousNode.setNext(null);	//将前驱结点的next指针更新为Null
		}
	}
	
	/**
	 * 根据给定位置删除链表元素
	 * @param position
	 */
	public void remove(int position) {
		int size = listLength();
		if(position > size-1 || position < 0) {
			System.err.println("指定索引非法,请在0~"+(size-1)+"范围内指定");
		}
		if(headNode.getNext() == null) {	//如果只有一个结点直接将头部结点赋值为null
			headNode  = null;
		}
		if(position == 0) {	//从头部删除
			removeFromHead();
		}
		if(headNode != null && headNode.getNext() != null) {
			int count = 1;
			ListNode previousNode = headNode;	//前驱结点
			while(position != count) {	//寻找前驱结点
				count++;
				previousNode = previousNode.getNext();
			}
			ListNode currentNode = previousNode.getNext();	//创建临时结点,值为前驱结点的next指针所指向的值
			previousNode.setNext(currentNode.getNext()); 	//更新前驱结点next指针指向的值为上面的临时结点
			
		}
	}
	
	/*------------------------------------------------单向链表获取长度、打印链表、获取链表指定位置值-------------------------------------------*/

	/**
	 * 获取单向链表的长度
	 * 
	 * @return 返回单向链表的长度
	 */
	public int listLength() {
		ListNode tempNode = headNode;
		int count = 0;
		while (tempNode != null) {
			count++;
			tempNode = tempNode.getNext();
		}
		return count;
	}

	/**
	 * 打印链表
	 */
	public void printList() {
		ListNode tempNode = headNode;
		if(tempNode == null) {
			System.out.println("空链表");
		}
		while (tempNode != null) {
			System.out.print("  " + tempNode.getData());
			tempNode = tempNode.getNext();
		}
		System.out.println();
	}
	
	/**
	 * 通过索引获取链表的值
	 * @param index 索引值 从0开始
	 * @return 返回对应索引的数据
	 */
	public Object get(int index) {
		int size = listLength();
		if(index > size-1 || index < 0) {
			System.err.println("索引非法,请输入0~"+(size-1)+"范围内的值");
		}
		ListNode tempNode = headNode;
		int count = 0;
		while(index != count) {
			tempNode = tempNode.getNext();
			count++;
		}
		return tempNode.getData();
	}

}


Test.java

package bokeyuan.linked;

public class Test {

	public static void main(String[] args) {
		SingleList list = new SingleList();	
		for(int i=0; i<10; i++) {
			list.add("item-"+i,i);
		}
		//打印测试
		System.out.println("打印链表");
		list.printList();
		//从头部添加测试
		System.out.println("从头部给链表添加数据");
		list.addFromHead("fromHeadFunction");
		list.printList();
		//从尾部添加
		System.out.println("从尾部给链表添加数据");
		list.addFromTail("fromTailFunction");
		list.printList();
		//从指定位置添加值
		System.out.println("从指定位置添加值");
		list.add("customPosition", 2);
		list.printList();
		//获取指定位置值
		System.out.println("获取指定位置值");
		String data = (String) list.get(2);
		System.out.println(data);
		//从头部删除
		System.out.println("从头部删除");
		list.removeFromHead();
		list.printList();
		//从尾部删除
		System.out.println("从尾部删除");
		list.removeFromTail();
		list.printList();
		//指定位置删除
		System.out.println("指定位置删除");
		list.remove(3);
		list.printList();
		//获取链表长度
		System.out.println("此时链表长度为");
		System.out.println(list.listLength());
		
		
	}
}

运行结果:

打印链表
  item-0  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9
从头部给链表添加数据
  fromHeadFunction  item-0  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9
从尾部给链表添加数据
  fromHeadFunction  item-0  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9  fromTailFunction
从指定位置添加值
  fromHeadFunction  item-0  customPosition  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9  fromTailFunction
获取指定位置值
customPosition
从头部删除
  item-0  customPosition  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9  fromTailFunction
从尾部删除
  item-0  customPosition  item-1  item-2  item-3  item-4  item-5  item-6  item-7  item-8  item-9
指定位置删除
  item-0  customPosition  item-1  item-3  item-4  item-5  item-6  item-7  item-8  item-9
此时链表长度为
10

posted @ 2021-02-19 00:33  胡海龙  阅读(339)  评论(0)    收藏  举报
www.huhailong.vip