2CPP线性表

本文深入探讨了数据结构中的线性表,包括其概念、基本操作和抽象类定义。接着详细介绍了顺序表的实现,包括构造函数、插入、删除等操作。单链表和双链表的实现同样被阐述,讨论了它们的结点结构、插入、删除等操作。此外,文章还提到了数据结构的操作性能分析和编程实践,涉及序列操作的处理。
摘要由CSDN通过智能技术生成

TODO:太晚了,明日调试

引言:

本文以线性表为例,介绍数据结构

目录

正文:

2.线性表

线性表的概念
  • 线性表是处理线性结构的数据结构。
  • 线性结构的数据指的是:
    • 除了A_0A0和A_{n-1}A**n−1外,每个元素都有唯一的前趋和后继
    • 对于每个A_iA**i,它的前驱是A_{i-1}A**i−1,它的后继是A_{i+1}A**i+1
    • A_0A0只有后继没有前驱,A_{n-1}A**n−1只有前驱没有后继。
线性表的基本操作
  • 创建空的线性表
  • 清除所有元素
  • 求线性表的长度
  • 在第i个位置插入一个元素
  • 删除第i个位置的元素
  • 搜索某个元素在线性表中是否出现
  • 访问线性表的第i个元素
  • 遍历线性表
线性表的抽象类

因此,我们可以这样定义线性表的抽象类,类中的每个函数都是纯虚函数,在实现数据结构时需要去完成每一个纯虚函数。

  • 和线性表的操作相比,少了创建操作,因为这可以用每个类的构造函数来完成。
  • 多了虚析构函数,是为了防止内存泄漏,把申请的动态内存析构掉。
template <class elemType>
class list {
  public: 
     // 清除
     virtual void clear() = 0;
     // 长度
     virtual int length() const = 0;
     // 插入
     virtual void insert(int i, const elemType &x) = 0; 
     // 删除
     virtual void remove(int i) = 0;
     // 搜索
     virtual int search(const elemType &x) const = 0;
     // 访问
     virtual elemType visit(int i) const = 0;
     // 遍历
     virtual void traverse() const = 0;
     // 析构
     virtual ~list() {};
};

顺序表:

顺序表的概念和设计

顺序表指的是线性表的顺序存储,也就是说线性表中结点存放在存储器上一块连续的空间中,即一个数组。

img

  • 数据成员
    • 表元素类型的指针 data
    • 数组规模 maxSize
    • 数组中的元素个数 currentLength
顺序表类定义
template <class elemType>
class seqList: public list<elemType> { 
private:
    elemType *data;
    int currentLength;
    int maxSize;
    // 为了解决插入元素时,数组容量不够的问题
    void doubleSpace();
public:
    seqList( int initSize = 10 );
    // 析构函数:还掉动态空间
    ~seqList( )  {  delete [] data;  }
    // 清空数组元素:只要把表长设为0
    void clear( )  {   currentLength = 0;   }
    // 数组长度:只需要返回这个私有成员变量currentLength
    int length( ) const  {   return currentLength;   }
    void insert( int i, const elemType &x); 
    void remove( int i );  
    int search( const elemType &x ) const ;
    // 访问数组元素:返回下标为i的数组元素
    elemType visit( int i) const { return data[i]  ; }
    void traverse( ) const ;
};
顺序表成员函数的实现
  • 构造函数:申请一个空表
template <class elemType>
seqList<elemType>::seqList(int initSize) {
    data = new elemType[initSize]; 
    maxSize = initSize; 
    currentLength = 0;
} 
  • 搜索函数:用for循环进行查找
template <class elemType>
int seqList<elemType>::search (const elemType &x) const {
    int i;
    // 空循环,不满足循环条件时跳出
    for (i = 0; i < currentLength && data[i] != x; ++i) ;
    if (i == currentLength)
        return -1; 
    else return i;
}
  • 遍历函数
template <class elemType>
void seqList<elemType>::traverse() const {
    cout << endl;
    for (int i = 0; i < currentLength; ++i) 
        cout << data[i] << ' ';
}
  • 插入函数
template <class elemType>
void seqList<elemType>::insert(int i, const elemType &x) {
    // 如果数组放满了,先扩大数组空间
    if (currentLength == maxSize) 
        doubleSpace(); 
    // 从后往前,元素后移
    for ( int j = currentLength; j > i; j--) 
        data[j] = data[j-1];
    // 空出空间,插入元素,表长加1
    data[i] = x;
    ++currentLength;
}
  • doubleSpace函数:扩大数组空间
template <class elemType>
void seqList<elemType>::doubleSpace() { 
    // 保存指向原来空间的指针
    elemType *tmp = data;
    // 重新申请空间
    maxSize *= 2;
    data = new elemType[maxSize];
    // 拷贝原有数据
    for (int i = 0; i < currentLength; ++i) 
        data[i] = tmp[i];
    // 清除原来申请的空间
    delete [] tmp;
} 
  • 删除函数:
template <class elemType>
void seqList<elemType>::remove(int i) {
    // 后面所有元素前移,表长减1
    for (int j = i; j < currentLength - 1; j++) 
        data[j] = data[j+1] ;
    --currentLength;
} 
总结
  • 由于要保持逻辑次序和物理次序的一致性,顺序表在插入删除时需要移动大量的数据,性能不太理想;
  • 由于逻辑次序和物理次序的一致性使得定位访问的性能很好
  • 顺序表比较适合静态的、经常做线性访问的线性表。
编程实践:

练习4·代码题

序列操作1

现有一个空的整数序列,需要你对其进行如下操作:

  1. 若命令为1 x,表示需要在这个序列末尾新插入整数x
  2. 若命令为2 x,表示需要删除从前向后第x个数字,数据保证x一定小于等于当前序列中整数的个数。
  3. 若命令为3 x,表示需要输出从前向后第x个数字,数据保证x一定小于等于当前序列中整数的个数。

输入描述:

第一行一个整数m,表示操作的次数。数据保证m ≤ 10^3

接下来m行,每行两个整数,第一个数字表示该次操作的类型,第二个数字x表示该此操作的参数,具体如题目所表述。

输出描述:

一行,若干个整数,为命令3输出的数字,用空格隔开。

示例 1:

输入:

6
1 1
1 2
1 3
2 2
3 1
3 2

输出:

1 3

代码:

#include <iostream>

using namespace std; 

int a[1010], cnt;

int main() {
    int m, t, x;
    cin >> m;
    while (m--) {
        cin >> t >> x;
        if (t == 1)
            a[++cnt] = x;
        else if (t == 2) {
            for (int i = x; i < cnt; i++)
                a[i] = a[i + 1];
            cnt--;
        }
        else
            cout << a[x] << " ";
    }
    return 0;
}

单链表

单链表的概念和设计

img

  • 单链表的概念:在单链表中,每个结点由数据元素和指针构成。该指针指向它的直接后继结点,最后一个结点的后继指针为空,即没有后继节点。
  • 单链表类的设计:
    • 存储设计:需要定义一个结点类,单链表类的数据成员包括头指针和链表长度currentLength
    • 工具函数设计:
      • 线性表的抽象函数都需要实现;
      • 链表的插入、删除操作都需要将指针移到被操作结点的前一结点。通过设计函数move实现找到某一个结点的位置的功能。
单链表类定义
template <class elemType>
class sLinkList: public list<elemType> { 
    private:
        // 定义一个结点的结构体,里面的所有成员都是公有的
        struct node {                                  
            elemType data;
            node *next;
            node(const elemType &x, node *n = NULL) { data = x; next = n; }
            node( ):next(NULL) { }
            ~node() {};
        };
 
        node  *head;                                         
        int currentLength;                                  
        node *move(int i) const;                  

    public:
        // 构造函数:创建空的单链表
        sLinkList() { 
            head = new node;
            currentLength = 0;
        }
        // 析构函数:调用clear把单链表的所有结点都还掉,再把头结点还掉
        ~sLinkList() { clear(); delete head; } 
        void clear() ;
        // 表的长度:返回私有的成员变量
        int length() const { return currentLength; }
        void insert(int i, const elemType &x); 
        void remove(int i);  
        int search(const elemType &x) const  ;
        elemType visit(int i) const;
        void traverse() const ;  
}; 
单链表成员函数实现
  • clear():把单链表变成一个空表。注意需要把所有结点的空间还给系统。
template <class elemType>
void sLinkList<elemType>::clear() { 
    node *p = head->next, *q;
    
    head->next = NULL;
    // 只要p不是空的,把p的下一个结点记下来,删掉p,再把q赋给p
    while (p != NULL) {     
        q = p->next;
        delete p;
        p=q;
    }
    currentLength = 0;
}                
  • insert(i,x):在第i个位置插入元素x。先让指针指向第i-1个元素,之后执行插入过程,即申请一个存放x的结点,让该结点的后继指针指向结点i,让第i-1个结点的后继指针指向这个新结点。
template <class elemType>
void sLinkList<elemType>::insert(int i, const elemType &x) {
    node *pos;
    
    // 通过move函数找到第i-1个元素的地址
    pos = move(i - 1);
    pos->next = new node(x, pos->next);
    ++currentLength;
}
  • move(i): 返回指向第i个元素的指针。
template <class elemType>
sLinkList<elemType>::node * sLinkList<elemType>::move(int i) const {
    node *p = head;  
    while (i-- >= 0) p = p->next;
    return p;
}
  • remove(i):删除第i个位置的元素。先找到第i-1个结点,让该结点的后继指针指向第i+1个结点,释放被删结点空间。
template <class elemType>
void sLinkList<elemType>::remove(int i) {
    node *pos, *delp;
    
    // 通过move函数找到第i-1个元素的地址
    pos = move(i - 1);
    // 找到被删结点的位置,让第i-1个元素的后继指针指向第i+1个结点
    delp = pos->next;
    pos->next = delp->next;
    // 释放被删结点的空间
    delete delp;
    --currentLength;
}
  • search(x):搜索某个元素在线性表中是否出现。从头指针的后继结点开始往后检查链表的结点直到找到x或查找到表尾。
template <class elemType>
int sLinkList<elemType>::search(const elemType &x) const {
    // 指针指向第一个元素A_0
    node *p = head->next;
    
    int i = 0;
    while (p != NULL && p->data != x) {
        p = p->next; 
        ++i;
    }
    // p为NULL表示找到表尾都没有找到
    if (p == NULL)
        return -1;
    else 
        return i;
}
  • visit(i):访问线性表的第i个元素。通过move(i)找到第i个结点,返回该结点的数据部分
template <class elemType>
elemType sLinkList<elemType>::visit(int i) const {
    return move(i)->data;
}
  • traverse():遍历运算。从头结点的直接后继开始重复:输出当前结点值,将后继结点设为当前结点,直到当前节点为空。
template <class elemType>
void sLinkList<elemType>::traverse() const {
    node *p = head->next;
    cout << endl;
    while (p != NULL) {
        cout << p->data << "  ";
        p=p->next;
    }
    cout << endl;
}
编程实践

序列操作2

现有若干个空的整数序列,需要你对其进行如下操作:

  1. 若命令为1 x y,表示需要在第x个序列末尾新插入整数y
  2. 若命令为2 x,表示需要在第x个序列末尾删除一个数字,删除前保证该序列不为空。
  3. 若命令为3,表示需要查询当前所有序列中的所包含的元素,每个序列单独一行,对于每个序列按照输入的顺序输出。

数据保证命令3总数不超过2条。

输入描述:

第一行两个整数n m,表示序列的的数量和操作的次数。数据保证n ≤ 10^5 m ≤ 5 * 10^5

接下来m行,每行表示一条命令,具体如题目所表述。

输出描述:

每次执行命令3输出n行,每行包含若干整数并用空格隔开,若该序列为空,则此行输出none

示例 1:

输入:

2 7
1 1 3
3
1 1 4
1 2 1
1 2 2
2 1
3

输出:

3
none
3
1 2
#include <iostream>

#define N 500010

using namespace std; 

int head[100010], nxt[N], val[N], out[N], cnt;

int main() {
    int n, m, t, x, y;
    cin >> n >> m;
    while (m--) {
        cin >> t;
        if (t == 1) {
            cin >> x >> y;
            val[++cnt] = y;
            nxt[cnt] = head[x];
            head[x] = cnt;
        }
        else if (t == 2) {
            cin >> x;
            head[x] = nxt[head[x]];
        }
        else {
            for (int i = 1; i <= n; i++) {
                int oc = 0, pos = head[i];
                while (pos) {
                    out[++oc] = val[pos];
                    pos = nxt[pos];
                }
                if (oc == 0)
                    cout << "none";
                while (oc)
                    cout << out[oc--] << " ";
                cout << endl;
            }
        }
    }
    return 0;
}

双链表

双链表的概念和设计
  • 双链表概念:在连接实现中,如果每个结点既保存直接后继结点的地址,也保存直接前驱结点的地址,则该链表被称为双链表。

img

  • 双链表类的设计:
    • 链表类的数据成员:头指针、尾指针,链表长度currentLength
    • 必须定义一个结点类。
    • 链表类必须实现线性表的所有操作,故链表类从线性表的抽象类继承。
    • 链表的插入、删除操作都需要将指针移到被操作结点的前一结点。通过设计函数move实现。
双链表的类定义
template <class elemType>
class dLinkList: public list<elemType> {
    private:
        struct node {                                         
            elemType  data;
            node *prev, *next; 
            node(const elemType &x, node *p = NULL, node *n = NULL) { data = x; next = n; prev = p; }
            node( ):next(NULL), prev(NULL) {}
            ~node() {}
        };

        node *head, *tail;                 
        int currentLength;  
        node *move(int i) const;
        
    public:
        dLinkList();
        // 析构函数:先调用clear删除所有元素,再删除头尾结点
        ~dLinkList() {
            clear(); 
            delete head; 
            delete tail;
        }

        void clear();
        // 返回元素数量
        int length() const { return currentLength; }
        void insert(int i, const elemType &x); 
        void remove(int i);  
        int search(const elemType &x) const;
        elemType visit(int i) const;
        void traverse() const;  
};
双链表成员函数实现
  • 构造函数:创建一个双链表就是创建一个只有头尾结点的链表,其中头结点的前驱为空,尾结点的后继为空。
template <class elemType>
dLinkList<elemType>::dLinkList() { 
    head = new node;
    head->next = tail = new node;
    tail->prev = head;
    currentLength = 0;
}
  • 插入:让指针指向第i个元素,执行插入过程,具体方式基本与单链表一致。
template <class elemType>
void dLinkList<elemType>::insert(int i, const elemType &x) {
    node *pos, *tmp;

    pos = move(i);                  
    tmp = new node(x, pos->prev, pos);
    pos->prev->next = tmp;        
    pos->prev = tmp;            

    ++currentLength;
}
  • 删除:让指针指向第i个元素,执行删除过程,具体方式基本与单链表一致。
template <class elemType>
void dLinkList<elemType>::remove(int i) {
    node *pos;

    pos = move(i);                       
    pos->prev->next = pos->next;           
    pos->next->prev = pos->prev

    delete pos;
    --currentLength;
}
  • 双链表其他操作和单链表基本相同。
编程实践

队列安排

一个学校里老师要将班上N个同学排成一列,同学被编号为1∼N,他采取如下的方法:

  1. 先将1号同学安排进队列,这时队列中只有他一个人;
  2. 2−N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1 ~ (i-1)中某位同学(即之前已经入列的同学)的左边或右边;
  3. 从队列中去掉M (M<N)个同学,其他同学位置顺序不变。

在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

输入描述:

1行为一个正整数N,表示了有N个同学。

2−N行,第i行包含两个整数k p,其中k为小于i的正整数,p0或者1。若p0,则表示将i号同学插入到k号同学的左边,p1则表示插入到右边。

N+1行为一个正整数M,表示去掉的同学数目。

接下来M行,每行一个正整数x,表示将x号同学从队列中移去,如果x号同学已经不在队列中则忽略这一条指令。

输出描述:

一行,包含最多N个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。

示例 1:

输入:

4
1 0
2 1
1 0
2
3
3

输出:

2 4 1
#include <cstdio> 
#include <cstring> 
#include <iostream> 
#include <algorithm> 
#define N 100005 
using namespace std; 

struct node { 
    int l, r; 
}nod[N]; 

int head, tail; 
void add(int a, int b, int c) { 
    nod[a].r = b; 
    nod[b].r = c; 
    nod[c].l = b; 
    nod[b].l = a; 
} 

void del(int a, int b, int c) { 
    nod[a].r = c; 
    nod[c].l = a; 
    nod[b].r = nod[b].l = 0; 
} 

int n, m; 
int main() { 
    scanf("%d", &n); 
    head = n + 1;  
    tail = n + 2; 
    nod[head].r = tail; 
    nod[tail].l = head; 
    add(head, 1, tail); 

    for (int i = 2; i <= n; ++i) { 
        int x, y; 
        scanf("%d%d", &x, &y); 
        if (!y) add(nod[x].l, i, x); 
        else add(x, i, nod[x].r); 
    } 
    scanf("%d", &m); 
    for (int i = 0; i < m; ++i) { 
        int x; 
        scanf("%d", &x); 
        if (!nod[x].l) continue; 
        del(nod[x].l, x, nod[x].r); 
    } 

    for (int i = nod[head].r; i != tail; i = nod[i].r) printf("%d ", i); 
    return 0; 
}

视频知识点

P1知识点
数据结构研究什么?
  • 一组有特定关系的数据的存储处理
  • 抽象的方法
数据的逻辑结构
  • 集合结构
  • 线性结构
  • 树形结构
  • 图形结构
数据结构的操作
  • 创建
  • 清除
  • 插入
  • 删除
  • 搜索
  • 更新
  • 访问
  • 遍历
P2知识点
  • 存储实现
    • 需要储存的信息
      • 一组数据元素
      • 数据元素之间的关系
    • 物理结构
      • 存储结点
      • 数据元素之间的关系的存储
      • 附加信息
    • 关系的存储
      • 顺序存储
      • 链接存储
      • 哈希存储
      • 索引存储
  • 运算实现
    • 操作怎么实现
      • 每个运算对应一个算法
      • 每个算法用一个函数表示
      • 每个数据结构有一组函数
P3知识点
  • 时间性能
    • 时间性能的衡量
    • 标准操作
    • 时间复杂度
      • 最好情况的时间复杂度
      • 最坏情况的时间复杂度
      • 平均情况的时间复杂度
    • 大O表示法(上界)
    • 两个定理
      • 求和定理
      • 求积定理
  • 空间性能
    • 空间复杂度
      • 算法处理过程中所需的额外工作量
      • 一般按最坏情况处理
      • 用大O表示法
p4知识点
  • 算法优化问题提出
    • 慢慢分析,逐步优化
    • 最大连续子序列和问题
  • O(N^3)O(N3)的算法
    • 枚举法

使用朴素的枚举法求最大连续子序列和的时间复杂度?

O(N^3)

P5知识点
知识点
  • O(N^2)O(N2)

    的算法

    • 枚举子序列

使用枚举子序列法求最大连续子序列和的时间复杂度?

P6知识点
  • 集合结构
  • 线性结构
  • 树形结构
  • 图形结构
数据结构的操作
  • 创建
  • 清除
  • 插入
  • 删除
  • 搜索
  • 更新
  • 访问
  • 遍历
P2知识点
  • 存储实现
    • 需要储存的信息
      • 一组数据元素
      • 数据元素之间的关系
    • 物理结构
      • 存储结点
      • 数据元素之间的关系的存储
      • 附加信息
    • 关系的存储
      • 顺序存储
      • 链接存储
      • 哈希存储
      • 索引存储
  • 运算实现
    • 操作怎么实现
      • 每个运算对应一个算法
      • 每个算法用一个函数表示
      • 每个数据结构有一组函数
P3知识点
  • 时间性能
    • 时间性能的衡量
    • 标准操作
    • 时间复杂度
      • 最好情况的时间复杂度
      • 最坏情况的时间复杂度
      • 平均情况的时间复杂度
    • 大O表示法(上界)
    • 两个定理
      • 求和定理
      • 求积定理
  • 空间性能
    • 空间复杂度
      • 算法处理过程中所需的额外工作量
      • 一般按最坏情况处理
      • 用大O表示法
p4知识点
  • 算法优化问题提出
    • 慢慢分析,逐步优化
    • 最大连续子序列和问题
  • O(N^3)O(N3)的算法
    • 枚举法

使用朴素的枚举法求最大连续子序列和的时间复杂度?

O(N^3)

P5知识点
知识点
  • O(N^2)O(N2)

    的算法

    • 枚举子序列

使用枚举子序列法求最大连续子序列和的时间复杂度?

P6知识点
P7知识点
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值