zoukankan      html  css  js  c++  java
  • 【线性表3】线性表的链式实现:单链表

    简介

    特点
    1、 用一组地址任意的存储单元 存储数据元素。存储单元地址 可连续,也可不连续。为了形成逻辑线性结构,每一个结点 除了保存需要存储的数据外,还需要保存逻辑上相邻的下一个结点的地址。
    2、链表由n个类型相同的结点通过指针链接形成线性结构。结点由数据域和指针域组成。数据域用于存储结点代表的数据,指针域存储结点的后继结点地址。
    3、链表不支持随机访问。有n个结点的线性表,访问某个结点的平均时间复杂度为O(n/2),最坏为O(n) 。而数组支持随机访问,他的访问时间复杂度为O(1)
             
     
    优点:插入和删除操作无需移动元素,只需修改结点的指针域。这点恰巧是顺序表(如ArrayList和数组)的缺点。
    缺点:访问元素时,不支持随机访问。访问第n个数据元素,必须先得到第n-1个元素的地址,因此访问任何一个结点必须从头结点开始向后迭代寻找,直到找到这个目标结点为止。 
     
     
    结点:由数据域和指针域构成。数据域用来存储数据元素,而指针域用来保存逻辑上相邻的下一个结点的地址。
    头结点:也叫哑结点(dummy node)。为了方便,一般情况下,我们都在链表的第一个逻辑位置上使用一个头结点。它的数据域不保存数据,而指针域保存表的第一个结点的地址,他相当于一个标记结点。

    带头结点的单链表的示意图

     如下是一个保存 char类型,数据元素依次为 【'A' , 'B' , 'C'】的,带头结点的链表的结构图。

    链表的主要操作

    • 追加结点(尾插法和头插法追加)
    • 插入结点(在指定的索引位置插入一个结点)
    • 删除结点(删除一个指定索引的结点)
    • 访问结点(get/set)
    • 遍历结点

     代码实现

     初始状态

    使用成员字段headNode 代表头结点(ListNode结构体对象),初始状态指针域为NULL。
    使用成员字段pLastNode保存链表的最后一个结点地址,这样在执行append操作时,就不必循环了。
    使用成员字段size保存链表实时长度。
     
    在执行clear操作后,链表会恢复到这个状态。
    #include<iostream>
    #include<cstdlib>
    #include<stdexcept>   
    using namespace std;
    struct ListNode
    {
        int element;
        ListNode* next;
        
        ListNode(int e=0,ListNode* nxt=0):element(e),next(nxt)
        {
        }
        
    };
    class LinkeList
    {
    private:
        ListNode  headNode;      //头结点
        ListNode* pLastNode;     //保存最后一个结点的地址
        int size;                //表实际长度
        
    public:
        LinkeList():headNode(0,0),pLastNode(0),size(0)
        {
            pLastNode = &headNode;
        }
        
        ~LinkeList()
        {
            //析构函数:释放所有的数据结点
            clear();
        }
        /*
        * 功能:删除索引为index 的结点  
        * 时间复杂度O(n)
        */
        bool remove(int index)
        {
            ListNode *p = &headNode;
            ListNode *p_delete;
            int i=0;
            
            if(index <0) return false;
            
            //循环用于获取 待删除结点的前一个结点的指针 。
            //用 i<index 去限制循环执行的次数
            while(p!=0 && i<index)
            {
                p= p->next;
                i++;
            }
            
            //退出循环后,合法情况下,p为待删除结点的前一个结点的指针
            //因此p 和 p->next 都不能为空 ,否则就是因为参数index不合法
            if(p==0 || p->next==0)
                return false;
                
            
            p_delete = p->next;    
            if(p_delete->next==0)
                pLastNode = p;   //如果删除的是最后一个结点,则更新pLastNode
            p->next = p_delete->next;
            delete p_delete;
            size --;
            
            
            return true;
        }
        
        
        /*
        * 功能:将新元素e包装为结点,插入到索引为index 的地方。
        * 时间复杂度O(n)
        */
        
        bool insert(int index , int e)
        {
            ListNode *p = &headNode;
            ListNode *p_new;
            int i=0;
            
            if(index <0) return false;
            
            //循环用于获取 待插结点的前一个结点的指针
            //用 i<index 去限制循环执行的次数
            while(p!=0 && i<index)
            {
                p= p->next;
                i++;
            }
            
            //退出循环后,合法情况下,p为待插结点的前一个结点的指针
            //因此p 不能为空 ,否则就是因为参数index不合法,index过大 。
            //但是p->next可以为空,如果是空,则相当于末尾追加append 。如果不为空,则是在中间位置插入。
            if(p==0) return false;
            
            p_new = new ListNode(e,p->next);  
            if(p->next == 0)
                pLastNode = p_new;   //更新pLastNode
            p->next = p_new;
            
            size++;
            
            
            return true;
        }
        
        
        
        /*
        * 功能:在链表末尾追加一个元素。
        */
        void append(int e)
        {
            ListNode*new_node = new ListNode(e,0);  //构造新结点
            
            pLastNode->next = new_node;
            pLastNode = new_node;    
            size++;
        }
        
        int length()const
        {
            return size;
        }
        
        int indexOf(int e)const
        {
            ListNode *p = headNode.next;
            
            for(int i=0;p!=0;++i,p=p->next){
                if(p->element == e) return i;
            }
            return -1;   // not found
        }     
        
        bool isEmpty()const
        {
            return size == 0;
        }
        
        
        /*
        * 功能:删除所有的数据结点,清空表
        */
        void clear()
        {
            ListNode*p = headNode.next;
        
            ListNode* t;
            while(p!=0){
                t = p;
                p = p->next;
                delete t;
            }
            //回归初始状态
            headNode.next= 0;
            pLastNode = &headNode;
            size = 0;
        }
        
        void show()const
        {
            ListNode*p = headNode.next;
            
            cout<<"[";
            while(p!=0){
                if(p!=headNode.next)
                    cout<<',';
                cout<<p->element;
                p=p->next;
            }
            cout<<"]";   
        }
        
        int operator[](size_t index)const
        {
            ListNode*p = headNode.next;
            int i=0;
            
            if( index <0 || index >= size  )
                throw std::out_of_range(0);
            
            while(p!=0 && i<index){
                p = p->next;
                i++;        
            }
            return p->element;
            
        }
        int& operator[](size_t index)
        {
            ListNode*p = headNode.next;
            int i=0;
            
            if( index <0 || index >= size  )
                throw std::out_of_range(0);
            
            while(p!=0 && i<index){
                p = p->next;
                i++;        
            }
            return p->element;
        }
        
    };
    int main()
    {
        LinkeList list;
        
        
        list.append(1);
        list.append(2);
        list.append(6);
        
        
        list.insert(2,3);
        list.insert(3,4);
        list.insert(4,5);
        list.insert(5,5);
        
        cout<<"len = "<<list.length()<<endl;
        
        
        list.show(); cout<<"
    ";
        
        list.remove(6);
        list.remove(5);
        
        list.show(); cout<<"
    ";
        
        list.append(100);
        
        list.show(); cout<<"
    ";
        
        return 0;
    }

    获取指定索引处的元素

     链表不支持随机访问。因此时间复杂度为O(n)。这是单链表不可避免的缺点。 

    int& operator[](size_t index)
    {
            ListNode*p = headNode.next;
            int i=0;
            
            if( index <0 || index >= size  )   //索引越界
                throw std::out_of_range(0);
            
            while(p!=0 && i<index){   //循环找到结点的指针
                p = p->next;
                i++;        
            }
            return p->element;
    }

    插入元素

    插入元素前,需要先获取待插入位置的前一个结点的地址。在插入时,先连接后结点,在连接前结点,这样就避免使用临时变量了。
    在已知待插入位置的前一个结点的地址情况下,时间复杂度为O(1)

    删除元素 

    删除元素前,需要先获取待删除位置的前一个结点的地址。
    在已知待删除位置的前一个结点的地址情况下,时间复杂度为O(1)

    小提示

    1、从理论上说,链表的优点就是因为他插入和删除等更改元素位置的操作很高效,时间复杂度为O(1) ,但实际上,由于我们在使用线性表时,是基于索引的,我们总是用索引标识一个结点,而不是他的地址,因此这就削弱了链表的优势。例如为了删除索引为n的结点,我们必须从头结点开始循环,找到索引为n-1的结点的地址,然后才能执行删除操作。所以从这方面看,实际操作时时间复杂度依然是O(n)。但是,修改指针比移动大量结点元素快多了,所以通常这也不是太大的问题。
     
     
    2、链表是不支持随机访问的,因此,对于链表,如果我们想对所有的结点执行某种操作,不应该使用传统的 for 循环,而应该使用迭代器,因为迭代器只需完成一次性循环,避免反复循环。下面是一Java集合框架中的LinkedList做测试,可以明显感受二者的差距,使用迭代器遍历比使用for循环快近10倍。
    public class DataStructure
    {
    
        public static void main(String[] args)
        {
            LinkedList<Integer> list = new LinkedList<Integer>();
            
            for(int i=0;i<100000;++i)
            {
                list.add(i);
            }
            
            useIterator(list);
            //useForLoop(list);
            
        }
        
        
        //耗时:4950ms
        public static void useForLoop(LinkedList<Integer> list)
        {
            long s = System.currentTimeMillis();
            
            for (int i = 0; i < list.size(); i++)
            {
                System.out.println(list.get(i)); 
            }
            long e = System.currentTimeMillis();
            
            System.out.println("耗时:"+(e-s) + "ms");
            
        }
        
        //耗时:546ms
        public static void useIterator(LinkedList<Integer> list)
        {
            
            long s = System.currentTimeMillis();
            
            for(Integer i:list)
            {
                System.out.println(i);
            }
            long e = System.currentTimeMillis();
            
            
            System.out.println("耗时:"+(e-s) + "ms");
        }
    }
    测试代码

    练习

    1、实现单链表的选择排序

    2、实现单链表的头插法添加数据

    3、实现反转单链表(空间复杂度为O(1))

    /*代码省去了前面已经实现的部分*/
    class LinkeList
    {
    
    //...
    
    public:
    
    //...     
        //选择排序 
        void selectSort()
        {
            ListNode *aim = headNode.next;  
            ListNode *min;
            ListNode *p;
            int t;
            
            if(aim==0) return;   //空表无需排序 
            
            while(aim->next!=0)
            {
                min = aim;      //假设当前比较对象结点aim是最小的 
                p = aim->next;
                while(p!=0)
                {
                    if(min->element  >  p->element){
                        min = p;
                    }
                    p = p->next;    
                }
                if(min != aim)  //最小元素易主了 
                {
                    t = min->element;
                    min->element = aim->element;
                    aim->element = t;        
                }
                
                aim = aim->next;    
            }
        
        }
        
        
        //头插法添加数据 :将数据添加为链表的第一个结点中 
        void addFirst(int e)
        {
            ListNode*new_node = new ListNode(e,headNode.next);  //构造新结点 
            headNode.next = new_node;
            size++;
        }
        
        //反转单链表 
        void revserse()
        {
            ListNode* pre=0;
            ListNode* cur = headNode.next;
            ListNode* nxt = cur->next;
            
            if(size < 1) return ;
            
            while(nxt!=0)
            {
                cur->next = pre;
                pre = cur;
                cur = nxt;
                
                nxt = nxt->next;    
            
            }
            
            cur->next = pre;
            headNode.next = cur;
               
        } 
        
    };
  • 相关阅读:
    prototype
    JS中我们为什么要new个实例而不直接执行
    购物车,实现增删改查;无bug,还有一个直接修改购物车数量功能未实现
    jquery中判断复选框有没有被选上
    git
    scss
    gulp基本操作
    nodejs,,一些基本操作--server。js
    node.js介绍及简单例子
    自己定义jquery插件轮播图
  • 原文地址:https://www.cnblogs.com/lulipro/p/7385428.html
Copyright © 2011-2022 走看看