【数据结构复习】线性存储结构

线性存储结构总结

代码不保证正确,知识点不保证全覆盖,随便写的。

顺序表

核心:

  1. recap函数——重新定义capacity
  2. 溢出的时候,两倍增加,删除同理

template用法

template
C++模板是一种泛型编程的技术,可以让函数或类的类型参数化,从而实现代码的复用和灵活性。
C++模板对于函数和类有以下区别:

  1. 函数模板是由编译器在处理函数调用时自动实例化的,而类模板需要用户手动指定模板参数来实例化。即:每一次都需要写List <T> l(举例),调用的时候需要List <int> a
  2. 函数模板可以根据函数调用时传入的实参来推断出模板参数的类型,而类模板不能。
  3. 函数模板可以重载,而类模板不能。
  4. 在类模板中,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 <typename T> class Array { ... };
    • 在类体中使用虚拟类型参数来定义类成员,例如:T *data; int size;
    • 在类外定义类成员函数时,需要在函数名前加上类名和模板参数列表,例如:template <typename T> Array<T>::Array(int n) { ... }
    • 在使用类模板时,需要显式地指定模板参数的类型,并用尖括号包围,例如:Array<int> arr(10);

代码示例:

#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运算符分配的内存空间。

  1. 如果要回收单个对象的内存空间,用delete;
  2. 如果要回收一组对象的内存空间,用delete[]。
内存调用
  1. 简单的复制(浅复制),可能出现内存问题,需要使用深复制。
  2. 指针初始化为空指针不然可能出事情
  3. 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的时候挨个删除

猜你喜欢

转载自blog.csdn.net/qq_39440588/article/details/129141145