3.链表

1、单向链表

1.1 链表的介绍

链表在内存中的存储

特点

  • 链表是以节点的方式来存储,是链式存储
  • 每个节点包含 data 域 和 next 域。next域用来指向下一个节点
  • 链表的各个节点不一定是连续存储的
  • 链表分带头节点的链表没有头节点的链表,根据实际的需求来确定

带头结点的逻辑示意图

1.2 实现思路

创建(添加)

  • 先创建一个Head头节点,表示单链表的头
  • 后面我们每添加一个节点,就放在链表的最后

遍历

  • 通过一个辅助变量,来遍历整个链表

有序插入

  • 先遍历链表,找到应该插入的位置
  • 要插入的节点的next指向插入位置的后一个节点
  • 插入位置的前一个节点的next指向要插入节点
    • 插入前要判断是否在队尾插入

根据某个属性节点修改值

  • 先遍历节点,找到修改的位置
    • 如果未找到修改节点,则不修改

删除某个节点

  • 先遍历节点,找到要删除节点的前一个节点
  • 进行删除操作

求倒数第n个节点的信息

  • 遍历链表,求出链表的有效长度length(不算头结点)
  • 遍历链表到第length-n的节点

翻转链表

  • 创建一个新的头结点,作为新链表的头
  • 从头遍历旧链表,将遍历到的节点插入新链表的头结点之后
  • 注意需要用到两个暂存节点
    • 一个用来保存正在遍历的节点
    • 一个用来保存正在遍历节点的下一个节点

逆序打印

  • 遍历链表,将遍历到的节点入栈
  • 遍历完后,进行出栈操作,同时打印出栈元素

代码

public class Demo1 {
	public static void main(String[] args) {
		LinkedList linkedList = new LinkedList();
		linkedList.traverseNode();
		System.out.println();
		//创建学生节点,并插入链表
		StudentNode student1 = new StudentNode(1, "Nyima");
		StudentNode student3 = new StudentNode(3, "Lulu");
		linkedList.addNode(student1);
		linkedList.addNode(student3);
		linkedList.traverseNode();
		System.out.println();

		//按id大小插入
		System.out.println("有序插入");
		StudentNode student2 = new StudentNode(0, "Wenwen");
		linkedList.addByOrder(student2);
		linkedList.traverseNode();
		System.out.println();

		//按id修改学生信息
		System.out.println("修改学生信息");
		student2 = new StudentNode(1, "Hulu");
		linkedList.changeNode(student2);
		linkedList.traverseNode();
		System.out.println();

		//根据id删除学生信息
		System.out.println("删除学生信息");
		student2 = new StudentNode(1, "Hulu");
		linkedList.deleteNode(student2);
		linkedList.traverseNode();
		System.out.println();

		//获得倒数第几个节点
		System.out.println("获得倒数节点");
		System.out.println(linkedList.getStuByRec(2));
		System.out.println();

		//翻转链表
		System.out.println("翻转链表");
		LinkedList newLinkedList = linkedList.reverseList();
		newLinkedList.traverseNode();
		System.out.println();

		//倒叙遍历链表
		System.out.println("倒序遍历链表");
		newLinkedList.reverseTraverse();

	}
}

/**
 * 创建链表
 */
class LinkedList {
	//头节点,防止被修改,设置为私有的
	private StudentNode head = new StudentNode(0, "");

	/**
	 * 添加节点
	 * @param node 要添加的节点
	 */
	public void addNode(StudentNode node) {
		//因为头节点不能被修改,所以创建一个辅助节点
		StudentNode temp = head;
		//找到最后一个节点
		while (true) {
			//temp是尾节点就停止循环
			if(temp.next == null) {
				break;
			}
			//不是尾结点就向后移动
			temp = temp.next;
		}
		//现在temp是尾节点了,再次插入
		temp.next = node;
	}

	/**
	 * 遍历链表
	 */
	public void traverseNode() {
		System.out.println("开始遍历链表");
		if(head.next == null) {
			System.out.println("链表为空");
		}
		//创建辅助节点
		StudentNode temp = head.next;
		while(true) {
			//遍历完成就停止循环
			if(temp == null) {
				break;
			}
			System.out.println(temp);
			temp = temp.next;
		}
	}

	/**
	 * 按id顺序插入节点
	 * @param node
	 */
	public void addByOrder(StudentNode node) {
		//如果没有首节点,就直接插入
		if(head.next == null) {
			head.next = node;
			return;
		}
		//辅助节点,用于找到插入位置和插入操作
		StudentNode temp = head;
		//节点的下一个节点存在,且它的id小于要插入节点的id,就继续下移
		while (temp.next!=null && temp.next.id < node.id) {
			temp = temp.next;
		}
		//如果temp的下一个节点存在,则执行该操作
		//且插入操作,顺序不能换
		if(temp.next != null) {
			node.next = temp.next;
		}
		temp.next = node;
 	}

	/**
	 * 根据id来修改节点信息
	 * @param node 修改信息的节点
	 */
 	public void changeNode(StudentNode node) {
		if(head == null) {
			System.out.println("链表为空,请先加入该学生信息");
			return;
		}
		StudentNode temp = head;
		//遍历链表,找到要修改的节点
		while (temp.next!= null && temp.id != node.id) {
			temp = temp.next;
		}
		//如果temp已经是最后一个节点,判断id是否相等
		if(temp.id != node.id) {
			System.out.println("未找到该学生的信息,请先创建该学生的信息");
			return;
		}
		//修改学生信息
		temp.name = node.name;
	}

	/**
	 * 根据id删除节点
	 * @param node 要删除的节点
	 */
	public void deleteNode(StudentNode node) {
 		if(head.next == null) {
			System.out.println("链表为空");
			return;
		}
 		StudentNode temp = head.next;
 		//遍历链表,找到要删除的节点
 		if(temp.next!=null && temp.next.id!=node.id) {
 			temp = temp.next;
		}
 		//判断最后一个节点的是否要删除的节点
 		if(temp.next.id != node.id) {
			System.out.println("请先插入该学生信息");
			return;
		}
 		//删除该节点
 		temp.next = temp.next.next;
	}

	/**
	 * 得到倒数的节点
	 * @param index 倒数第几个数
	 * @return
	 */
	public StudentNode getStuByRec(int index) {
		if(head.next == null) {
			System.out.println("链表为空!");
		}
		StudentNode temp = head.next;
		//用户记录链表长度,因为head.next不为空,此时已经有一个节点了
		//所以length初始化为1
		int length = 1;
		while(temp.next != null) {
			temp = temp.next;
			length++;
		}
		if(length < index) {
			throw new RuntimeException("链表越界");
		}

		temp = head.next;
		for(int i = 0; i<length-index; i++) {
			temp = temp.next;
		}
		return temp;
	}

	/**
	 * 翻转链表
	 * @return 反转后的链表
	 */
	public LinkedList reverseList() {
		//链表为空或者只有一个节点,无需翻转
		if(head.next == null || head.next.next == null) {
			System.out.println("无需翻转");
		}
		LinkedList newLinkedList = new LinkedList();
		//给新链表创建新的头结点
		newLinkedList.head = new StudentNode(0, "");
		//用于保存正在遍历的节点
		StudentNode temp = head.next;
		//用于保存正在遍历节点的下一个节点
		StudentNode nextNode = temp.next;
		while(true) {
			//插入新链表
			temp.next = newLinkedList.head.next;
			newLinkedList.head.next = temp;
			//移动到下一个节点
			temp = nextNode;
			nextNode = nextNode.next;
			if(temp.next == null) {
				//插入最后一个节点
				temp.next = newLinkedList.head.next;
				newLinkedList.head.next = temp;
				head.next = null;
				return newLinkedList;
			}
		}
	}

	public void reverseTraverse() {
		if(head == null) {
			System.out.println("链表为空");
		}

		StudentNode temp = head.next;
		//创建栈,用于存放遍历到的节点
		Stack<StudentNode> stack = new Stack<>();
		while(temp != null) {
			stack.push(temp);
			temp = temp.next;
		}

		while (!stack.isEmpty()) {
			System.out.println(stack.pop());
		}
	}
}

/**
 * 定义节点
 */
class StudentNode {
	int id;
	String name;
	//用于保存下一个节点的地址
	StudentNode next;

	public StudentNode(int id, String name) {
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "StudentNode{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
}

结果

开始遍历链表
链表为空

开始遍历链表
StudentNode{id=1, name='Nyima'}
StudentNode{id=3, name='Lulu'}

有序插入
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=1, name='Nyima'}
StudentNode{id=3, name='Lulu'}

修改学生信息
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=1, name='Hulu'}
StudentNode{id=3, name='Lulu'}

删除学生信息
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=3, name='Lulu'}

获得倒数节点
StudentNode{id=0, name='Wenwen'}

翻转链表
开始遍历链表
StudentNode{id=3, name='Lulu'}
StudentNode{id=0, name='Wenwen'}

倒序遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=3, name='Lulu'}

2、双向链表

2.1 双向链表

2.2 实现思路

遍历

  • 和单向链表的遍历相同,需要一个辅助节点来保存当前正在遍历的节点

添加

  • 双向链表多出了一个frnot,所以在添加时,要让新增节点的front指向链表尾节点

修改

  • 和单向链表的修改相同

删除

  • 使用temp来保存要删除的节点
  • temp.pre.next指向temp.next
  • temp.next指向temp.pre

代码

public class Demo2 {
   public static void main(String[] args) {
      BidirectionalList bidirectionalList = new BidirectionalList();
      bidirectionalList.addNode(new PersonNode(1, "Nyima"));
      bidirectionalList.addNode(new PersonNode(2, "Lulu"));
      bidirectionalList.traverseNode();
      System.out.println();

      System.out.println("修改节点信息");
      bidirectionalList.changeNode(new PersonNode(2, "Wenwen"));
      bidirectionalList.traverseNode();
      System.out.println();

      //删除节点
      System.out.println("删除节点");
      bidirectionalList.deleteNode(new PersonNode(1, "Nyima"));
      bidirectionalList.traverseNode();
   }
}

class BidirectionalList {
   private final PersonNode head = new PersonNode(-1, "");

   /**
    * 判断双向链表是否为空
    * @return 判空结果
    */
   public boolean isEmpty() {
      return head.next == null;
   }

   /**
    * 添加将诶点
    * @param node 要被添加的节点
    */
   public void addNode(PersonNode node) {
      PersonNode temp = head;
      if(temp.next != null) {
         temp = temp.next;
      }
      //插入在最后一个节点的后面
      temp.next = node;
      node.front = temp;
   }

    public void traverseNode() {
       System.out.println("遍历链表");
      if (isEmpty()) {
         System.out.println("链表为空");
         return;
      }
      PersonNode temp = head.next;
      while(temp != null) {
         System.out.println(temp);
         temp = temp.next;
      }
    }

   /**
    * 修改节点信息
    * @param node 要修改的节点
    */
    public void changeNode(PersonNode node) {
      if(isEmpty()) {
         System.out.println("链表为空");
         return;
      }
      PersonNode temp = head.next;
      //用于判定是否做了修改
      boolean flag = false;
      while (temp != null) {
         if(temp.id == node.id) {
            //匹配到节点,替换节点
            temp.front.next = node;
            node.next = temp.next;
            flag = true;
         }
         temp = temp.next;
      }
      if(!flag) {
         System.out.println("未匹配到改人信息");
      }

    }

   /**
    * 删除节点
    * @param node 要删除的节点
    */
    public void deleteNode(PersonNode node) {
      if(isEmpty()){
         System.out.println("链表为空");
         return;
      }
      PersonNode temp = head.next;
      //查看是否删除成功
       boolean flag = false;
      while(temp != null) {
         if(temp.id == node.id) {
            temp.front.next = temp.next;
            temp.next = null;
            flag = true;
         }
         temp = temp.next;
      }
      if(!flag) {
         System.out.println("未找到该节点");
      }
    }


}

class PersonNode {
   int id;
   String name;
   //指向下一个节点
   PersonNode next;
   //指向前一个节点
   PersonNode front;

   public PersonNode(int id, String name) {
      this.id = id;
      this.name = name;
   }

   @Override
   public String toString() {
      return "PersonNode{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
   }
}

输出

遍历链表
PersonNode{id=1, name='Nyima'}
PersonNode{id=2, name='Lulu'}

修改节点信息
遍历链表
PersonNode{id=1, name='Nyima'}
PersonNode{id=2, name='Wenwen'}

删除节点
遍历链表
PersonNode{id=2, name='Wenwen'}Copy

3、循环链表

3.1 循环链表

单链表的尾节点指向首节点,即可构成循环链表

3.2 约瑟夫环

N个人围成一圈,从第S个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉,求出被杀顺序

  • 例如N=6,M=5,S=1,被杀掉的顺序是:6,4,5,2,1,3

大致思路

  • 遍历链表找到指定位置的节点

  • 用一个front保存指定节点的前一个节点,方便删除

  • 当count==time时,删除此时正在遍历的节点,放入数组中,并将count的值初始化

  • 用一个变量loopTime记录已经出圈了几个人,当其等于length时则是最后一个节点,直接放入数组并返回数即可

代码

public class Demo3 {
	public static void main(String[] args) {
		CircularList circularList = new CircularList();
		AttenderNode node1 = new AttenderNode(1);
		AttenderNode node2 = new AttenderNode(2);
		AttenderNode node3 = new AttenderNode(3);
		AttenderNode node4 = new AttenderNode(4);
		circularList.addNode(node1);
		circularList.addNode(node2);
		circularList.addNode(node3);
		circularList.addNode(node4);
		System.out.println("约瑟夫环");
		AttenderNode[] arr = circularList.killAttender(1, 4);
		for(AttenderNode node : arr) {
			System.out.println(node);
		}


	}
}

class CircularList {
	private final AttenderNode head = new AttenderNode(-1);

	AttenderNode temp;

	public void addNode(AttenderNode node) {
		if(head.next == null) {
			head.next = node;
			return;
		}
		temp = head.next;
		//只有一个节点,还没成环
		if(temp.next == null) {
			temp.next = node;
			node.next = head.next;
			return;
		}
		while (temp.next != head.next) {
			temp = temp.next;
		}
		//temp现在为尾节点
		temp.next = node;
		//node现在为尾节点,将其next指向首节点
		node.next = head.next;
	}

		public int getListLength() {
			if(head.next == null) {
				return 0;
			}
			//判断是否只有一个节点
			if(head.next.next == null) {
				return 1;
			}
			//节点个数初始为2
			int length = 2;
			AttenderNode first = head.next;
			AttenderNode temp = first.next;
			while(true) {
				//循环了一轮
				if(temp.next.id == first.id) {
					return length;
				}
				temp = temp.next;
				length++;
			}
		}

	/**
	 * 删除指定位置节点
	 * @param time 次数
	 * @param start 开始节点
	 * @return
	 */
		public AttenderNode[] killAttender(int time, int start) {
			if(head.next == null) {
				System.out.println("链表为空");
				return null;
			}
			temp = head.next;
			int length = getListLength();
			//存放退出队列的节点
			AttenderNode[] arr = new AttenderNode[length];
			//从start开始计数
			if(start > length) {
				System.out.println("超出链表范围");
				return null;
			}
			AttenderNode startNode = temp;
			int count;
			//如果只有一个节点,直接返回
			if(temp.next == null) {
				arr[0] = temp;
				return arr;
			}
			//找到开始节点位置
			for(count = 1; count<start; count++) {
				startNode = startNode.next;
			}
			//找到start的前一个节点,方便删除操作
			AttenderNode front = startNode.next;
			while(front.next != startNode) {
				front = front.next;
			}
			//开始选出节点出链表
			temp = startNode;
			//记录循环次数
			int loopTime = 1;
			int index = 0;
			for(count=1; count<=time; count++) {
				if(loopTime == length) {
					//放入最后一个节点
					arr[index] = temp;
					return arr;
				}
				if(count == time) {
					arr[index] = temp;
					front.next = temp.next;
					index++;
					loopTime++;
					//初始化,因为在循环开始时还会+1,所以这里初始化为0
					count = 0;
				}
				temp =front.next;
			}
			return arr;
		}


}

class AttenderNode {
	int id;
	AttenderNode next;

	@Override
	public String toString() {
		return "KillerNode{" +
				"id=" + id +
				'}';
	}

	public AttenderNode(int id) {
		this.id = id;
	}
}

运行结果

约瑟夫环
KillerNode{id=4}
KillerNode{id=1}
KillerNode{id=2}
KillerNode{id=3}

 

posted @ 2022-03-06 14:50  随遇而安==  阅读(19)  评论(0编辑  收藏  举报