Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

数据结构(3)--循环链表

$
0
0

单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环。

和单链表相比,循环单链表的长处是从链尾到链头比较方便。当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表。

和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为方便。
循环链表与单链表最重要的区别是:尾结点的指针,不再是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 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>