データ構造とアルゴリズム(X)循環リスト

循環リスト


各ノードは唯一の動作を停止後方テール・ポインタのチェーンへのポインタバック、終端ノードにおける単一のリンクリストのポインタを格納するための単一リンクされたリストのために、最初のノードにヌルポインタから変更され、原因環を形成する全体単鎖、単方向と呼ばれるようなリストのテール循環リスト、円形のリストをいいます

空のリスト

非空のリスト

ここで最初のノードの最初のノードは、仮想的であるが、実際には、円形のリンクリストと単一リンクされたリストとの間の主な違いは、ループの決意条件は、原稿があることであると判断P->次の空であり、それは今、P>次のヘッダに等しくないですノードは、ループが終了していません

Java言語で片道円形リンクリスト、初期化ステートメントのヘッドとテールポインタは、リストの有効なレコード・サイズ内の要素の可変数があります。そして、単一のリストとして、循環リストも使用したノードは、我々は2つのドメインのデータと属性データポインタフィールドを作成する内部クラスノード、宣言と初期化ノードの次の

package DS02.动态链表;

import DS01.动态数组.List;
import java.util.Iterator;

//单向循环链表
public class LinkedSingleLoop<E> implements List<E> {
    private Node head;
    private Node rear;
    private int size;
    public LinkedSingleLoop(){
        head=null;
        rear=null;
        size=0;
    }
    
    //内部类
    class Node{   //链表内部的东西,内部类私有,外部不需要知道结点的存在
        E data;  //数据域      类型由外界决定
        LinkedSingleLoop.Node next; //指针域
        //构造函数
        Node(){
            this(null,null);
        }
        Node(E data, LinkedSingleLoop.Node next){
            this.data=data;
            this.next=next;
        }
        
        @Override
        public String toString() {
            return data.toString();     //由调用者决定,结点的toString
        }
    }
    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size==0;
    }

要素を挿入する方法については、要素は、我々は実際のヘッドノード、挿入要素だけでなく、特殊なケースで行う循環リストのために、最初にそれを別の考えを入れ、ここで、新しいノードを作成し、データフィールド内のノードに格納されています、リストは、私たちの最初のグラフである、空である、それは個別に説明することができ、空の宣告しました。

静止要素インサート挿入三つの方法、第一プラグ、端部プラグと中間インサートされ、コードに関連する操作で、言わない、私の前記一本鎖で、先に議論されているにも詳細なコメントです。

    @Override
    public void add(int index, E e) {
        if(index<0||index>size){
            throw new IllegalArgumentException("角标越界");
        }
        Node n=new Node();  //创新结点
        if(isEmpty()){     //初始为空时,头尾指针都指向新结点
            n.data=e;      //同时新结点数据域存放元素e
            head=n;
            rear=n;
            rear.next=head;
        }else if(index==size){  //尾插
            n.next=rear.next;       //先把尾结点的指针域中的地址(也就是头结点地址)给新结点的指针域 (联系头结点和新结点)
            rear.next=n;            //再把新结点的地址给尾结点的指针域 (联系尾结点和新结点)
            rear=n;                 //移动尾指针 (新结点变成新的尾结点)
        }else if(index==0){     //头插
            rear.next=n;            //先把新结点的地址给尾结点的指针域(联系尾结点和新结点)
            n.next=head;            //把头结点的地址给新结点n的指针(联系头结点和新结点)
            head=n;                 //移动头指针(新结点变成新的头结点)
        }else{                  //中间插入
            Node p=head;                //借助指针p找到指定角标index的前一个结点
            for(int i=0;i<index-1;i++){
                p=p.next;               //移动指针p
            }                           //循环结束时,p在index-1
            n.next=p.next;              //让新结点指针域指向后一个元素地址(建立后一个元素与新结点的联系)
            p.next=n;                   //让p处结点指针域指向新结点的地址(建立前一个结点与新结点的联系)
        }
        size++;       //有效元素+1
    }
    
    @Override
    public void addFirst(E e) {
        //头插
        add(0,e);
    }

    @Override
    public void addLast(E e) {
        //尾插
        add(size,e);
    }

、添字の対応する要素をポインタPによって、要素の値を変更するために、取得した要素対応する角度をSUPERSCRIPT得る来るされ、Oの時間複雑度(n)は、いくつかの特別な場合は、別途フッタヘッダを論じ、時間を効果的に低減することができます複雑

    //获取角标对应的元素
    @Override
    public E get(int index) {
        if(index<0||index>size){
            throw new IllegalArgumentException("角标越界");
        }
        if(index==0){             //元素在表头情况
            return head.data;
        }else if(index==size-1){    //元素在表尾情况
            return rear.data;
        }else{               //元素在中间的情况
            Node p=head;        //借助p指针遍历链表
            for(int i=0;i<index;i++){
                p=p.next;       //移动指针p
            }
            return p.data;   //返回p指针处结点的数据域
        }
    }

    @Override
    public E getFirst() {
        //获取表头
        return get(0);
    }

    @Override
    public E getLast() {
        //获取表尾
        return get(size-1);
    }
    
    //修改指定角标处元素的值,与get()方法类似
    @Override
    public void set(int index, E e) {
        if(index<0||index>size){
            throw new IllegalArgumentException("角标越界");
        }
        Node p=head;
        for(int i=0;i<index;i++){
            p=p.next;
        }
        p.data=e;
    }
    
    //判断是否包含元素e
    @Override
    public boolean contains(E e) {
        //调用find()方法,看是否能找到    
        return find(e)!=-1;
    }
    
    //获取元素e处的角标
    @Override
    public int find(E e) {
        Node p=head;    //借助指针p遍历
        for(int i=0;i<size;i++){
            if(p.data.equals(e)){   //对比指针p处数据域的元素是否参数e内容一样(这里不能用==比,==比的是地址,equals比的是内容)
                return i;   //返回角标
            }
            p=p.next;   //循环一次,指针p移动一次
        }
        return -1;
    }

除去要素は三つの方法、削除頭部、尾部及び中間体は削除削除、本質的に前部および後部接触接合ノードを除去するために切断され、それは回収され、繰り返さない、関連するオペレーションコードもに詳述されていますコメント。

  @Override
    public E remove(int index) {
        if(isEmpty()){
            throw new IllegalArgumentException("空表");
        }
        if(index<0||index>size){
            throw new IllegalArgumentException("角标越界");
        }
        E ret=null;		//变量ret用于接收返回删除的元素
        if(size==1){       //特殊情况:只有一个元素
            ret=head.data;		
            head=null;
            rear=null;
        }else if(index==size-1){    //尾删
            Node p=head;		
            while(p.next!=rear){    //p指针从头开始,p的下一个为尾结点时跳出
                p=p.next;  //p往后走
            }			   
            ret=rear.data;		//ret接收一下
            p.next=rear.next;	//让倒数第二个结点的指针域指向头结点地址(跳过尾结点)
            rear=p;		//更新尾指针
        }else if(index==1){     //头删
            ret=head.data;  //把元素值给ret用于返回
            rear.next=head.next;    //尾结点的指针域指向第二个元素
            head=head.next;     //头指针往后走,更新
        }else{
            Node p=head;        //借助指针p遍历
            for(int i=0;i<index-1;i++){
                p=p.next;
            }
            Node del=p.next;    //del存放要删除的结点
            ret=del.data;       //把结点的元素内容给ret返回
            p.next=del.next;    //p的指针域指向del的后一个元素,没有人知道del,被回收器回收
        }
        size--;     //有效元素个数递减
        return ret; //返回删除的元素
    }

    @Override
    public E removeFirst() {
    	//头删
        return remove(0);
    }

    @Override
    public E removeLast() {
    	//头删
        return remove(size-1);
    }

    //删除指定元素
    @Override
    public void removeElement(E e) {
        int index=find(e);		//先找再删
        if(index!=-1){			
            remove(index);
        }else{			//找不到对应角标时,抛异常
            throw new IllegalArgumentException("元素不存在");
        }
    }

引き継ぐために、リスト、コンストラクタコードを空にする

 @Override
    public void clear() {
        head=null;
        rear=null;
        size=0;
    }

反復子は、リンクされたリストの出力素子を通過さにわたって横断し、何度カレンダーを注文され、終了条件がないP->次の第1のノードに等しくない場合、のみP->次のノード端に等しくない場合、それは最後の要素を通過しないであろうここで、複数の移動可能な仮想は、次ポインタPを作成する最初のノードは、エンドノードに横断することができます。

//迭代器
    @Override
    public Iterator<E> iterator() {
        return new LinkedSingleLoopIterator();  //创建内部类对象
    }
    //内部类
    public class LinkedSingleLoopIterator implements Iterator<E>{
        Node p;
        //构造函数
        public LinkedSingleLoopIterator(){
            p=new Node();   //创建虚拟结点
            p.next=head;    //从头结点开始遍历
        }
        @Override
        public boolean hasNext() {
            return p.next!=rear;    //继续条件:p的下一节点不为rear,即尾结点
        }

        @Override
        public E next() {
            p=p.next;       //p指针移动
            return p.data;
        }
    }

このような循環リストのコードは、当然のことながら、各部分の機能は、すべてを見つけるのは簡単で、問題を回避するために終え、コードの正しさを保証するために、完成したテストクラスのテストでなければなりません。

发布了70 篇原创文章 · 获赞 56 · 访问量 1980

おすすめ

転載: blog.csdn.net/qq_43624033/article/details/103617972