线性存储结构总结
代码不保证正确,知识点不保证全覆盖,随便写的。
文章目录
顺序表
核心:
- recap函数——重新定义capacity
- 溢出的时候,两倍增加,删除同理
template用法
template
C++模板是一种泛型编程的技术,可以让函数或类的类型参数化,从而实现代码的复用和灵活性。
C++模板对于函数和类有以下区别:
- 函数模板是由编译器在处理函数调用时自动实例化的,而类模板需要用户手动指定模板参数来实例化。即:每一次都需要写
List <T> l
(举例),调用的时候需要List <int> a
- 函数模板可以根据函数调用时传入的实参来推断出模板参数的类型,而类模板不能。
- 函数模板可以重载,而类模板不能。
- 在类模板中,
template <typename T>
和template <class T>
没有区别
-
函数模板
- 使用关键字template和虚拟类型参数来声明函数模板,例如:
template <typename T> void swap(T &a, T &b);
- 在函数体中使用虚拟类型参数来定义函数模板²³,例如:
template <typename T> void swap(T &a, T &b) {T temp = a; a = b; b = temp;}
- 在调用函数模板时,可以显式或隐式地指定模板参数的类型,例如:
swap<int>(x, y);
或swap(x, y);
- 使用关键字template和虚拟类型参数来声明函数模板,例如:
-
类模板
- 使用关键字template和虚拟类型参数来声明类模板,例如:
template <typename T> class Array { ... };
- 在类体中使用虚拟类型参数来定义类成员,例如:
T *data; int size;
- 在类外定义类成员函数时,需要在函数名前加上类名和模板参数列表,例如:
template <typename T> Array<T>::Array(int n) { ... }
- 在使用类模板时,需要显式地指定模板参数的类型,并用尖括号包围,例如:
Array<int> arr(10);
- 使用关键字template和虚拟类型参数来声明类模板,例如:
代码示例:
#include <iostream>
#define initcap 100
using namespace std;
template <class T>
class List{
private:
T * data;
int capacity;
int length;
public:
List() {
data= new T[initcap];
capacity=initcap;
length=0;
}
List(const List<T> & l) {
capacity=l.capacity;
length=l.length;
data=new T[capacity];
for(int i=0;i<length;i++) {
data[i]=l.data[i];
}
}
~List(){
delete [] data;
}
void recap(int newcap) {//重新分配长度,需要复制之前的重新来过,记得删除分配的旧空间
if(newcap<=0) return ;
T* olddata=data;
data=new T[newcap];
for(int i=0;i<length;i++) {
data[i]=olddata[i];
}
delete [] olddata;
}
void addList(T* a,int n) {
for(int i=0;i<n;i++) {
if(length<capacity) recap(length*2);
data[length]=a[i];
length++;
}
}
void print() {
for(int i=0;i<length;i++) cout<<data[i]<<" ";
cout<<endl;
}
void Add(T node) {
if(length==capacity) recap(2*length);
data[length]=node;
length++;
}
int getLength() {
return length;
}
bool getElement(int pos,T &node){
if(pos<0||pos>=length) return false;
node=data[pos];
return true;
}
bool changeElement(int pos,T node){
if(pos<0||pos>=length) return false;
data[pos]=node;
return true;
}
int getNo(T node){
for(int i=0;i<length;i++) {
if(data[i]==node) return i;
}
return -1;
}
};
int main() {
List <int> testa;
int a[5]={1,2,3,4,5};
testa.addList(a,5);
testa.print();
List <int> testb(testa);
testb.print();
return 0;
}
注意
delete
C++里面delete运算符用于回收用new运算符分配的内存空间。
- 如果要回收单个对象的内存空间,用delete;
- 如果要回收一组对象的内存空间,用delete[]。
内存调用
- 简单的复制(浅复制),可能出现内存问题,需要使用深复制。
- 指针初始化为空指针不然可能出事情
- delete不能重复,不然会清空空的
复制构造函数在以下几种情况下会被调用:
- 当用一个类对象去初始化同类的另一个对象时,例如
Person p (q);
或Person p = q;
- 当一个类对象作为函数的参数进行值传递时,例如
f (p);
- 当从一个返回类对象的函数返回一个对象时,例如
return p;
- 当用花括号列表初始化一个数组的元素时,例如
Person arr[] = {p, q};
链表
template
语句LinkNode <T> * next;
:表示next是一个指向LinkNode 类型的指针
语句T * next;
:表示next是一个指向T类型的指针。如果T是一个基本数据类型,如int或char,那么后者就是一个普通的指针。如果T是一个复杂数据类型,如string或类,那么后者就是一个对象指针。
如果用“T * next;”,那么next只能指向T类型的数据,而不能指向LinkNode 类型的节点¹。这样就无法构成链表的结构,因为链表需要每个节点都有一个指向下一个节点的指针域¹³。如果用“LinkNode * next;”,那么next就可以指向LinkNode 类型的节点,从而形成链表。
T* next
相当于 next[]
代码
注意链表里面,head相当于是啥也没有的,要么让编号从-1到n-1,要么p=head->next
#include <iostream>
using namespace std;
template <typename T>
class LinkNode{
T data;
LinkNode <T> * next;
// T * next;
public:
LinkNode():next(nullptr){};
LinkNode(T d):data(d),next(nullptr){};
};
template <typename T>
class LinkList{
public:
LinkNode<T> *head;
LinkNode<T> *tail;
LinkList() {
head=tail=new LinkNode<T>();
}
~LinkList(){
LinkNode<T> *p;
LinkNode<T> *pre;
pre=head;
p=pre->next;
while(p!= nullptr) {
delete pre;
pre=p;
p=pre->next;
}
delete pre;
}
void addNodeFront(T *a,int n) {
for(int i=0;i<n;i++) {
LinkNode<T> *tmp= new LinkNode<T>(a[i]);
tmp->next=head->next;
head->next=tmp;
}
}
void addNodeTail(T *a,int n) {
LinkNode<T> t;
t=tail;
for(int i=0;i<n;i++) {
LinkNode<T> *tmp= new LinkNode<T>(a[i]);
tmp->next=t;
t=tmp;
}
t.next= nullptr;
tail=t;
}
{
LinkNode<T>* getNum(int num) { //注意这里
if(num<0) return NULL;
LinkNode<T>* p=head;
while(p!= nullptr&&num!=0) {
num--;
p=p->next;
}
return p;
}
};
双链表
略
循环链表
(循环双链表)
略
对比考虑
空间
存储密度 =结点中数据本身占用的存储量/整个结点占用的存储量
顺序表的存储密度为1,而链表的存储密度小于1。仅从存储密度 看,顺序表的存储空间利用率高。
顺序表需要预先分配初始空间,所有数据占用一整片地址连续的 内存空间,如果分配的空间过小,易出现上溢出,需要扩展空间 导致大量元素移动而降低效率;如果分配的空间过大,会导致空 间空闲而浪费。而链表的存储空间是动态分配的,只要内存有空闲,就不会出现上溢出。
结论:当线性表的长度变化不大,易于事先确定的情况下,为了节省存储空间,宜采用顺序表作为存储结构。当线性表的长度变 化较大,难以估计其存储大小时,为了节省存储空间,宜采用链 表作为存储结构。
时间
顺序表具有随机存取特性,给定序号查找对应的元素值的时间 为O(1),而链表不具有随机存取特性,只能顺序访问,给定序 号查找对应的元素值的时间为O(n)。
在顺序表中插入和删除操作时,通常需要平均移动半个表的元 素。而在链表中插入和删除操作仅仅需要修改相关结点的指针 成员,不必移动结点。
结论:若线性表的运算主要是查找,很少做插入和删除操作, 宜采用顺序表作为存储结构。若频繁地做插入和删除操作,宜 采用链表作为存储结构。
STL
vector
greater 和template很像?
vector
cmp()
list
循环双链表
栈
template <typename T>
class LinkNode{
T data;
LinkNode* next;
}
KMP算法
void getNextval(string s,int *nextval) {
int i=0,k=-1,n=s.length();
nextval[0]=-1;
while(i<n) {
if(k==-1||s[i]==s[k]) {
i++;
k++;
if(s[i]==s[k]) {//next函数少了这个
nextval[i]=nextval[k];
}
else nextval[i]=k;
}
else k=nextval[k];
}
}
int KMP(string s1,string s2,int *nextval) {
int n1=s1.length(),n2=s2.length(),i=0,j=0;
while(i<n1&&j<n2) {
if(j==-1||s1[i]==s2[j]) {
i++;
j++;
}
else {
j=nextval[j];
}
}
if(j>=n2) return i-n2;
else return -1;
}
数组
“随机存取特性”是指可以直接指定访问,而不必从头开始。
矩阵压缩
对称矩阵:
(下三角)从上到下,从左到右
Summary
表式结构
template <class T>
class List{//只需要这一个,因为*data相当于数组
private:
T * data;
int capacity;
int length;
public:
List() {
data= new T[initcap];//初始化是这样,
capacity=initcap;
length=0;
}
};
//如果要更改值,需要重新 data = new T[size]
//记得delete 原来的
链式结构
template <typename T>
class LinkNode{
private:
T data;
LinkNode * next;//可有可不有
};
class LinkList{
private:
LinkNode<T> *head;
LinkNode<T> *rear;//可有可不有
}
//新建节点的时候使用
//LinkNode<T> *tmp= new LinkNode<T>(num);
//删除delete的时候挨个删除