0
点赞
收藏
分享

微信扫一扫

C++算法与数据结构_18

kiliwalk 2022-04-22 阅读 69

知识点

线性表的定义

  • 线性结构
    • 除了 A 0 A_0 A0 A n − 1 A_{n-1} An1外,每个元素都有唯一的前趋和后继
    • 对于每个 A i A_i Ai,它的前驱是 A i − 1 A_{i-1} Ai1,它的后继是 A i + 1 A_{i+1} Ai+1
    • A 0 A_0 A0只有后继没有前驱, A n − 1 A_{n-1} An1只有前驱没有后继。
  • 线性表:处理线性结构的数据结构
  • 线性表的操作
    • 创建一个线性表create():创建一个空线性表
    • 清除一个线性表clear():删除线性表中的所有数据元素
    • 求线性表的长度length():返回线性表的长度
    • 在第i个位置插入一个元素insert(i,x):在i的位置插入元素xa[i]及之后的元素依次后移
    • 删除第i个位置的元素remove(i):删除i位置的元素,a[i+1]及之后的元素依次前移
    • 搜索某个元素在线性表中是否出现search(x):搜索x并返回其位置
    • 访问线性表的第i个元素visit(i):返回位置i的元素值
    • 遍历线性表运算traverse():按顺序返回每一个元素
  • 线性表的抽象类
template <class elemType> // 定义一个模板类
class list
{
public:
virtual void clear() = 0;
virtual int length() const = 0;
virtual void insert(int i,const elemType 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() {}; // 虚析构函数:解决父类指针指向子类对象时,释放子类对象的资源时,释放不完全,造成的内存泄漏问题。
}

顺序表

概念及类定义

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

L63YgP.png

  • 数据成员
    • 表元素类型的指针 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
void remove( int i );
int search( const elemType
// 访问数组元素:返回下标为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;
}

总结

  • 由于要保持逻辑次序和物理次序的一致性,顺序表在插入删除时需要移动大量的数据,性能不太理想;
  • 由于逻辑次序和物理次序的一致性使得定位访问的性能很好;
  • 顺序表比较适合静态的、经常做线性访问的线性表。

练习

序列操作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;
}

单链表

单链表的概念和设计

L6U4Lq.png

  • 单链表的概念:在单链表中,每个结点由数据元素和指针构成。该指针指向它的直接后继结点,最后一个结点的后继指针为空,即没有后继节点。
  • 单链表类的设计:
    • 存储设计:需要定义一个结点类,单链表类的数据成员包括头指针和链表长度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
void remove(int i);
int search(const elemType
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 x y,表示需要在第x个序列末尾新插入整数y。
  • 若命令为2 x,表示需要在第x个序列末尾删除一个数字,删除前保证该序列不为空。
  • 若命令为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;
}

双链表

双链表的概念和设计

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

Lc4GVK.png

  • 双链表类的设计:
    • 链表类的数据成员:头指针、尾指针,链表长度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
void remove(int i);
int search(const elemType
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号同学安排进队列,这时队列中只有他一个人;
  • 2−N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1 ~ (i-1)中某位同学(即之前已经入列的同学)的左边或右边;
  • 从队列中去掉M (M<N)个同学,其他同学位置顺序不变。
    在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

输入描述:

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

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

第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",
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",
if (!y) add(nod[x].l, i, x);
else add(x, i, nod[x].r);
}
scanf("%d",
for (int i = 0; i < m; ++i) {
int x;
scanf("%d",
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;
}
scanf("%d",
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",
if (!y) add(nod[x].l, i, x);
else add(x, i, nod[x].r);
}
scanf("%d",
for (int i = 0; i < m; ++i) {
int x;
scanf("%d",
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;
}
举报

相关推荐

0 条评论