单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环。
和单链表相比,循环单链表的长处是从链尾到链头比较方便。当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表。
和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为方便。
循环链表与单链表最重要的区别是:尾结点的指针,不再是p->next = null;而是:p->next=head。
主要的思想还是:我们要多次反复了解到我们假如添加了尾部节点,就要修改尾部的next指针域,让它指向heand头结点.
代码如下:
Node类:
/**
* 循环表节点
* @author Administrator
*
* @param <T>
*/
public class NewNode<T> {
private T data;
private NewNode next;
NewNode(T data, NewNode next) {
this.data = data;
this.next = next;
}
NewNode(T data) {
this(data,null);
}
NewNode() {
this(null,null);
}
public T getData() {
return this.data;
}
public NewNode getNext() {
return this.next;
}
public void setData(T data) {
this.data = data;
}
public void setNext(NewNode next) {
this.next = next;
}
public String toString() {
return getData().toString();
}
}
循环单链表:
/**
* 循环链表其实跟单链表实现差不多,不过在尾部的指针指向在判断的时候,我们之前是一旦有空就不进行下去,现在是判断最后一个位置不是头指针.
* @author Administrator
* @param <T>
*
*/
public class CycleLinkList<T> {
public NewNode<T> head,tail;//声明头尾节点
//构造函数,建立空表
public CycleLinkList() {
this(null);
}
public CycleLinkList(T data) {
if(data==null){
head=new NewNode<T>(data,null);//创建头节点 ,数据为data,指针为空
tail=head;//头尾节点一样
tail.setNext(head);//尾节点的指针域指向头结点
}else{
head=new NewNode<T>();
tail=new NewNode<T>(data, head);//定义头尾节点
head.setNext(tail);//这一个可能稍微难以理解,这里的其实就是在初始化一个空表,我们设定的tail其实指是一个动态声明的为指针
}
}
// 清空链表,头=尾,this = null;
public void clear() {
removeAll();
head.setNext(tail);
tail.setNext(head);
head = tail;
}
// 如果头=尾,返回true;否则,返回false
public boolean isEmpty() {
return tail == head;
}
// 获取链表当前元素个数,即表长
// 原理判断假如temp不是尾节点,就一直获取下去,知道temp是尾节点
public int Length() {
int len = 0;
NewNode<T> temp = head;//头结点
while(temp.getNext() != head) { //
len++;
temp = temp.getNext();
}
return len;
}
public T getElement(int index){
if(index<0){
return null;
}
NewNode<T> temp=head;
int j=0;
//这里主要是利用判断结点是否为最后的节点
while(j<=index&&temp.getNext()!=head){
j++;
temp=temp.getNext();
}
return temp.getData();
}
// 尾添
/**
* 思路:
* 1. 新建结点,设定next为head
* 2. 通过改变原来尾节点的next值进行改变
* 3. 将temp作为尾节点
* 注意:head、tail其实只是一个标志声明
* @param element
* @return
*/
public boolean add(T element) {
if(element == null) return false;
NewNode<T> temp = new NewNode<T>(element,head);
tail.setNext(temp);
tail = temp;
return true;
}
// 首添
/**
* 思路:
* 1. 新建结点,设定next为原来的head.next;
* 2. 将头结点的下一个坐标设置为新的点
*
* @param element
* @return
*/
public boolean addHead(T element) {
if(element == null) return false;
NewNode<T> temp = new NewNode<T>(element,head.getNext());
head.setNext(temp);
return true;
}
private void removeAll() {
// TODO Auto-generated method stub
}
// 任意位置添加
public boolean addByIndex(int index, T element) {
if(element == null) return false;
if(index <= 0)
addHead(element); //重点在意等于0而已
if(index >= Length())
add(element);
//中间插入
/**
* 思路:
* 1.分别获取结点的属性
* 2.结点属性分别修改
* 3.获取修改后的结点
* 4.设定结点跟在之前设定的结点后面
*/
if(index > 0 && index < Length()) {
int j = 0;
NewNode<T> temp = head;
NewNode<T> tempNext = temp.getNext();
//通过遍历获取到了相应的node
while(j < index && tempNext != head) {
j++;
temp = tempNext;
tempNext = tempNext.getNext();
}
NewNode<T> node = new NewNode<T>(element,tempNext);
temp.setNext(node);
}
return true;
}
// 删除指定位置上的元素
public T remove(int index) {
if(isEmpty()) return null;
T element = null;
if(index <= 0)
return removeHead();
if(index > Length())
index = Length()-1;
if(index > 0) {
int j = 0;
NewNode<T> temp = head;
NewNode<T> tempNext = head.getNext();
while(j < index && tempNext != head) {
temp = tempNext;
tempNext = tempNext.getNext();
j++;
}
element = tempNext.getData();
temp.setNext(tempNext.getNext());
}
return element;
}
// 删除表中的第一条element的数据
public T remove(T element) {
if(isEmpty()) return null;
if(element == null) return null;
NewNode<T> temp = head.getNext();
while(temp != head) {
if(!(element.equals(temp.getData()))) {
temp = temp.getNext();
}else{
return element;
}
}
return null;
}
// 删除表头数据
private T removeHead() {
if(isEmpty()) return null;
NewNode<T> firstNode = head.getNext();
T element = firstNode.getData();
head.setNext(head.getNext().getNext());
return element;
}
// 重写父类toString()方法
public String toString() {
if(isEmpty())
return "[ ]";
StringBuffer sb = new StringBuffer();
sb.append("[ ");
int L = Length();
for(int i = 0; i < L; i++) {
sb.append(getElement(i)+" ");
}
sb.append("]");
return sb.toString();
}
}
约瑟夫问题(单链表经典问题):
/**
* 有M个人,其编号分别为1-M。这M个人按顺序排成一个圈。
* 现在给定一个数N,从第一个人开始依次报数,数到N的人出列,
* 然后又从下一个人开始又从1开始依次报数,
* 数到N的人又出列...如此循环,直到最后所有人出列为止。
* 输出出列轨迹
* 约瑟夫问题
*
*解决思路:
*1 .定义两个LinkedList数组
*2. 数完一圈就获取到暂时出列的人,将出列的人放到临时数组
*3. 遍历临时数组,然后删除总数组中有着临时数组被选出的人
*4. 再清空临时数组,接着继续使用刚刚的方法
*
*/
//存放M
List<String> list = new LinkedList<String>();
//一圈数数完之后,临时存放出列的人
List<String> tmp = new LinkedList<String>();
public CycleLinkedTest_Josephus(int m)
{
for (int i = 1; i <= m; i++)
{
list.add(i + "");
}
}
/**
* 递归 执行主体
* @param start
* @param n
*/
public void start(int start,int n)
{
int size = list.size();
if (list.size() == 0)
{
System.out.println("结束!!");
return ;
}
for (int i = 1; i <= size; i++)
{
if ((i + start) % n == 0)
{
System.out.println(list.get(i - 1) + " 出局,");
tmp.add(list.get(i - 1));
}
}
//在m中删除临时队列的人
for (String str : tmp)
{
list.remove(str);
}
//清除list
tmp.clear();
//递归
start((size + start) % n,n);
}
public static void main(String[] args)
{
long t1=System.currentTimeMillis();
//M=100
CycleLinkedTest_Josephus cj = new CycleLinkedTest_Josephus(2);
//n=7
cj.start(0,3);
System.out.print("该算法花费时间:");
System.out.println(System.currentTimeMillis()-t1+"ms");
}
}
单链表的其他面试题:
1. 查找单链表中的倒数第k个结点:
这里我们需要一个概念,就是快慢指针的概念.笼统地说,就是定义两个指针同步行走,但是位置不一样却又有关联.
指针1-(first):起点为第一个结点
指针2-(seond):移动到k-1
当指针2移动到size,这时候指针1就会在k的位置啦.
public Node findLastNode(Node head, int k) {
if (k == 0 || head == null) {
return null;
}
Node first = head;
Node second = head;
//让second结点往后挪k-1个位置
for (int i = 0; i < k - 1; i++) {
System.out.println("i的值是" + i);
second = second.next;
if (second == null) { //说明k的值已经大于链表的长度了,做一次判断
//throw new NullPointerException("链表的长度小于" + k); //我们自己抛出异常,给用户以提示
return null;
}
}
//让first和second结点整体向后移动,直到second走到最后一个结点
while (second.next != null) {
first = first.next;
second = second.next;
}
//当second结点走到最后一个节点的时候,此时first指向的结点就是我们要找的结点
return first;
}
查找单链表中的中间结点(同样的思想):
这次是first走一步,second就走2步了.
//方法:查找链表的中间结点
public Node findMidNode(Node head) {
if (head == null) {
return null;
}
Node first = head;
Node second = head;
//每次移动时,让second结点移动两位,first结点移动一位
while (second != null && second.next != null) {
first = first.next;
second = second.next.next;
}
//直到second结点移动到null时,此时first指针指向的位置就是中间结点的位置
return first;
}
链表的翻转:
//方法:链表的反转
public Node reverseList(Node head) {
//如果链表为空或者只有一个节点,无需反转,直接返回原链表的头结点
if (head == null || head.next == null) {
return head;
}
Node current = head;
Node next = null; //定义当前结点的下一个结点
Node reverseHead = null; //反转后新链表的表头
while (current != null) {
next = current.next; //暂时保存住当前结点的下一个结点,因为下一次要用
current.next = reverseHead; //将current的下一个结点指向新链表的头结点
reverseHead = current;
current = next; // 操作结束后,current节点后移
}
return reverseHead;
}
作者:qq_32421769 发表于2016/9/3 17:08:18 原文链接
阅读:47 评论:0 查看评论