《数据结构与算法分析》学习笔记二——表、栈、队列

 线性表List

        线性表的实现有两种标准方法——顺序表array-based listsequential list)和链表linked list)。

        顺序表由数组实现,顺序存储大小事先固定,可能浪费大量空间,优点是可以随机访问O(1),但插入和删除为O(n);

        链表元素的个数就没有限制,不连续存储,链表的空间需求为O(n),访问元素为O(n),但插入和删除为O(1)。

        一般来说,当线性表元素数目变化较大或未知时,最好使用链表实现;而如果事先知道线性表的大致长度,使用顺序表的空间效率会更高。

        双链表存储了两个指针:一个指向它的后继结点,另一个指向它的前驱结点。故其可以从一个表结点出发,在线性表中随意访问它的前驱结点和后继结点。双链表与单链表相比唯一的缺点就是使用更多的空间。若数据无序,双向搜索与单向同速,但若数据有序,则双向搜索更快。

        循环链表为最后一个结点指向第一个结点的链表。因此,从表中任一结点出发都可以遍历整个链表。

         静态链表 为用数组描述的链表,游标实现法,用下个结点在数组中的ind代替指针,优点为插入和删除只需修改游标,不用移动元素,缺点没有解决连续存储带来的表长难确定问题,失去了顺序存储随机读取的特性。      

 

stack

        栈是限定仅在一端进行插入或删除操作的线性表。栈的可访问元素称为栈顶top)元素,元素插入栈称为入栈push),删除元素称为出栈pop)。栈被称为LIFOLast In First Out)线性表。栈的实现方法多种多样,其中有:顺序栈(array-based stack)和链式栈(linked stack)。当需要实现多个栈时,可利用顺序栈单向延伸的特性,即使用一个数组来存储两个栈,每个栈从各自的端点向中间延伸。但只有当两个栈的空间需求有相反关系才奏效,即一个栈增长时另一个栈缩短(从一个栈中取出元素放入另一个栈,这种方法非常有效)。实现顺序栈和链式栈的所有操作都只需要常数时间,而另一个比较基准是空间。初始时顺序栈必须说明一个固定长度,当栈不够满时,一些空间将浪费掉。链式栈的长度可变,但对于每个元素需要一个链接域,从而产生结构性开销。

        栈的应用:(1) 检查闭合(平衡符号) (2)可以模拟递归。在某些情况下,递归可以用迭代来代替。然而,并不总能用迭代来代替递归,当实现有多个分支的算法时,就很难用迭代来代替递归,而必须使用递归或与递归等价的算法。(3)四则运算表达式求值(中缀 后缀表达式转换)

 队列queue

       队列也是一种受限的线性表,其元素只能从队尾插入(enqueue)以及从队首删除(dequeue)。队列别称为FIFOFirst In First Out)线性表。

        假设有一个含有n个元素的顺序队列(array-base queue),为了出队时需要移动元素,设置front和rear两个指针分别指向队头元素和队尾元素的下一个位置,为了避免假溢出问题,将头尾相接,成为循环队列。

       (1) 队空时: front=rear   队头尾指针相遇

       (2)  队满时: (rear+1)%maxsize=front   队头指针为队尾指针的下一个

       (3) 队列长度为(rear-front+MAX)%MAX

        链式队列(linked queue)的实现只是对链表的实现做了简单的修改。若实现方法没有表头结点,需在队列函数中单独处理插入到空队列的特殊情形,以及dequeue函数导致的空队列的特殊情形。从本质上将,enqueue只是简单地把新元素放到链表尾部(rear指向的结点),然后修改rear指针指向新的链表结点;dequeue指示简单地去掉表中最前面一个结点并修改front指针。

        实现顺序队列和链式队列的所有成员函数都需要常数时间,空间比较问题与栈实现类似。只是,顺序队列不像顺序栈那样,不能在一个数组中存储两个队列,除非总有数据项从一个队列转入另一个队列。在顺序队列中存储变长记录的方式与顺序栈中的方式相同。

附关键代码:

3.5 两个有序链表的合并

    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL)
            return pHead2;
        if(pHead2 == NULL)
            return pHead1;
        ListNode* p = pHead1, *q = pHead2;
        ListNode* head = new ListNode(0), *cur = head;
        while(p != NULL && q != NULL)
        {
            if(p -> val <= q -> val)
            {
                cur -> next = p;
                p = p -> next;
            }
            else
            {
                cur -> next = q;
                q = q -> next;
            }
            cur = cur -> next;
        }
        cur -> next = p == NULL ? q : p;
        return head -> next;
    }

3.6 Josephus问题

http://tingyun.site/2018/04/26/%E7%BA%A6%E7%91%9F%E5%A4%AB%E7%8E%AF%E9%97%AE%E9%A2%98%E8%AF%A6%E8%A7%A3/

约定:

- Jq(n)表示n人构成的约瑟夫环,每次移除第q个人的解

- n个人的编号从0开始至n-1

            ç¤ºä¾å¾ï¼

新的约瑟夫环的下标都是在旧的下标基础上,减去一个q,再用计算出的结果对长度取余new = (old-q) % n,反推一下:
old = (new+q) % n

实战一下

  J2(1) = 0
  J2(2) = (J2(1) + 2) % 2 = 0
  J2(3) = (J2(2) + 2) % 3 = 2
  J2(4) = (J2(3) + 2) % 4 = 0
  J2(5) = (J2(4) + 2) % 5 = 2
……

int Josephus(int n, int q)
{
    if(n == 1)
        return 0; 
    else
        return (Josephus(n-1, q) + q) % n; 
}

3.12 单链表逆序

    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL || pHead -> next == NULL)
            return pHead;
        ListNode* dummyhead = new ListNode(-1);
        dummyhead -> next = pHead;
        ListNode* pre = dummyhead;
        ListNode* cur = pHead; 
        // 注意cur从始至终一直指向同一个结点,即头结点,直至头结点成为最后一个结点
        while(cur -> next != NULL)
        {
            ListNode* pnext = cur -> next;
            cur -> next = pnext -> next;
            pnext -> next = pre -> next;
            pre -> next = pnext;
        }
        return dummyhead -> next;
    }

猜你喜欢

转载自blog.csdn.net/qy724728631/article/details/81806515