数据结构与算法-顺序表

顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构顺序存储结构的线性表通常称为顺序表。

初始化

初始化顺序表

#include <cstdio>
#include <cstdlib>
#include <cstring>

typedef int DATA_TYPE; 

DATA_TYPE* pArr; // 保存顺序表的首地址
size_t len; // 顺序表元素个数

void init(); // 初始化

int main(int argc,char* argv[])
{
    init();
    return 0;
}

void init() {
    pArr = NULL;
    len = 0;
}

定义一个init函数负责初始化顺序表,将首地址赋值为NULL,顺序表长度置0

加入数据

定义一个push_back函数加入新的数据:

// 传引用是防止传参过程中产生临时对象
// const是避免变量在函数中被修改
void push_back(const DATA_TYPE& data) {
    DATA_TYPE* pNew = new DATA_TYPE[len+1];// 分配内存
    if (pArr != NULL) {
        memcpy(pNew,pArr,sizeof(DATA_TYPE)*len); // 内存拷贝
        delete[] pArr; // 释放内存
    } 
    pArr = pNew; // 更改首地址
    pArr[len++] = data; // 加入数据
}

push_back函数须要判断两种情况,一种是顺序表为空的情况,此时pArr为NULL,此时直接分配内存,并将数据添加到尾部,一种是顺序表不为空,此时将原先的数据拷贝到新开的内存中,再将数据添加到尾部

内存拷贝使用memcpy函数: void *memcpy(void *dest, const void *src, size_t n),第三个参数是拷贝的字节数

测试:

int main(int argc,char* argv[])
{
    init();
    for (int i=0;i<10;i++)
        push_back(i);
    for (int i=0;i<len;i++)
        printf("%d ",pArr[i]);
    printf("\n");
    return 0;
}

运行:

$ g++ demo.cpp -o demo && ./demo
0 1 2 3 4 5 6 7 8 9 

删除数据

分三种删除: 删第一个元素,删最后一个元素,删中间一个元素

删除第一个元素:

void pop_front() {
    // 遇到空表直接返回
    if (NULL == pArr)
        return;
    // 长度为1直接释放掉整个表
    if (1 == len) {
        delete[] pArr;
        pArr = NULL;
        len--;
        return;
    }

    DATA_TYPE *pNew = new DATA_TYPE[len-1]; // 分配新内存
    memcpy(pNew,pArr+1,sizeof(DATA_TYPE)*(len-1)); // 内存拷贝
    delete[] pArr; // 释放内存
    pArr = pNew; // 指向新内存
    len--; // 长度减小
}

先判断两种情况,表为空时则不作任何操作,表只有一个元素则释放掉表,其他情况下将除第一个元素以外的元素进行内存拷贝,拷贝到新开辟的一段内存

void pop_back() {
    if (NULL == pArr)
        return;
    if (1 == len) {
        delete[] pArr;
        pArr = NULL;
        len--;
        return;
    }

    DATA_TYPE *pNew = new DATA_TYPE[len-1]; // 分配新内存
    memcpy(pNew,pArr,sizeof(DATA_TYPE)*(len-1)); // 内存拷贝
    delete[] pArr; // 释放内存
    pArr = pNew; // 指向新内存
    len--; // 长度减小
}

删除最后一个元素和上述是同理的

删中间元素:

void delete_pos(size_t n) {
    if (NULL == pArr) {
        return;
    }
    if (n >= len) {
        return;
    }

    if (n == 0) {内存
        pop_front();
    } else if (n == len-1) {
        pop_back();
    } else {
        DATA_TYPE* pNew = new DATA_TYPE[len-1];
        // 内存拷贝
        memcpy(pNew,pArr,sizeof(DATA_TYPE)*n);
        memcpy(pNew+n,pArr+n+1,sizeof(DATA_TYPE)*(len-n-1));
        delete[] pArr; // 释放内存
        pArr = pNew;
        len--;
    }
}

首先判断是否为空表,如果是则退出,再判断是否指定的下标是否大于总长度,如果是则退出。如果下标对应的是第一个元素则调用pop_front,如果是最后一个则调用pop_back,其他情况下使用两段内存拷贝,除要删除的元素外,其他数据都拷贝到新的内存

性能优化

对于上述代码,时间上还可以优化,每次插入数据都要开辟内存和拷贝数据,有着不小的时间开销。但想要减少时间的开销,只能考虑以空间换时间。

现在每次申请内存时,都申请原来大小的1.5倍大小,或者申请(len+1)大小的内存,下面更改了push_back函数:

void push_back(const DATA_TYPE& data) {
    // 查看是否须要申请内存
    if (capacity <= len) {
        // 计算须要申请的内存大小
        capacity = capacity + (((capacity>>1)>1)?(capacity>>1):1);
        DATA_TYPE* pNew = new DATA_TYPE[capacity];// 分配内存
        if (pArr) {
            memcpy(pNew,pArr,sizeof(DATA_TYPE)*len); // 内存拷贝
            delete[] pArr; // 释放内存
        }
    } 
    pArr = pNew; // 更改首地址
    pArr[len++] = data; // 加入数据
}

只有当capcaity小于等于len时,才申请新的内存,这减少了申请内存的次数,虽然经常会浪费一点内存,但似乎是值得的

更新和查找

因为顺序表是数组形式存储,所以直接通过下表访问数组元素即可,较为简单,不做具体实现了

posted @ 2022-07-12 12:32  N3ptune  阅读(48)  评论(0编辑  收藏  举报