数据结构:线性表-动态数组

目录

1数据结构理论

1.1数据结构概念

1.2算法的概念

2线性表

2.1线性表基本概念

2.2线性表的顺序储存

2.2.1线性表顺序储存(动态数组)的设计和实现

初始化

插入

 遍历

删除(两种方式)

销毁

 

附录:

完整版本动态数组

 


1数据结构理论

1.1数据结构概念

数据结构是计算机存储,组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。

通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高校的检索算法和索引技术有关。

数据结构是计算机存储,组织数据的方式。

1.2算法的概念

算法是特定问题求解步骤的描述,在计算机中表现为指令的有限序列,算法是独立存在的一种解决问题的方法和思想。

对于算法而言,语言并不重要,重要的是思想。

2线性表

2.1线性表基本概念

线性结构是一种最简单且常用的数据结构。线性结构的基本特点是节点之间满足线性关系。

常见的,动态数组、链表、栈、队都属于线性结构。

他们的共同之处,是节点中有且只有一个开始节点和终端节点。

按照这种关系,可以把它们的所有节点排列成一个线性序列。

但是,他们分别属于几种不同的抽象数据类型实现,它们之间的区别,主要是操作的不同。

线性表是零个或者多个数据元素的有限序列,数据元素之间是有顺序的,数据元素个数是有限的,数据元素的类型必须相同

 

连续的空间储存——数组

非连续的空间储存——链表

2.2线性表的顺序储存

  1. 需要知道存储数据元素空间的首地址
  2. 元素的个数
  3. 数组的总容量(不够的时候需要扩容,所以需要知道容量)
  4. 数据类型(void *)

只存储数据的地址,不存成具体的内容,只是为了将数据整合。 

2.2.1线性表顺序储存(动态数组)的设计和实现

整体设计思路

     

动态数组的特点:

  1. 连续内存
  2. 储存的是void *类型变量
  3. 二级万能指针(void **)指向这段内存的首地址 

整个数据结构,需要一个指向这段连续内存的指针,这样才能访问这段内存,及void **

需要知道已经放入元素的个数和这段连续内存的大小(不时之需可能要扩容)

这三个要素构成了动态数组

动态数组的组成:

  1. 指向连续内存的首地址
  2. 连续内存容积
  3. 存储内容个数 

初始化

typedef struct DynamicArray
{
    //数据存储元素的空间首地址
    void **addr;
    //存储数据的内存中最大能够容纳多少元素
    int capacity;//容量
    //当前存储了多少个元素
    int size;
}DA;

所以根据上述内容,建立DynamicArray结构体 ,其中包含指向这片连续内存首地址的二级指针

整个内存的容量大小,目前存储元素的多少

具体接口如下: 

.h

DA * Init_dynamicarray(int capacity);//输入参数:容量大小

 .cpp

//初始化数组
DA * Init_dynamicarray(int capacity)
{
    if(capacity<=0)
    {
        return nullptr;
    }
    DA * arr = (DA *)malloc(sizeof(DA));
    if(nullptr == arr)
    {
        return nullptr;
    }
    arr->addr = (void **)malloc(sizeof(void *)*capacity );
    arr->capacity = capacity;
    arr->size = 0;

    return arr;
}

初始化函数,输入参数:这段内存的大小

如果小于或等于0,则判定为无效参数,return nullptr;

利用malloc给结构体分配空间(必须将内存分配到堆区中)

之后根据输入的参数,利用malloc分配与参数匹配的空间大小,并将首地址赋予二级指针 (addr)

自然,容量大小(capacity)就是传入的参数值

元素个数(size),初始化,自然是0

之后将地址返回,函数中创建的变量都在栈区,调用完函数后自然会释放。

 

插入

.h

void Insert(DA * arr,int pose,void * datasPointer);//输入参数:需要知道数组首地址,插入位置,插入内容

输入参数要求:动态数组结构体类,插入元素的位置,需要插入的元素的地址。 

注意:输入的位置是大于0的,也就是该数据在插入该数据后的整个新数据中的位置。

.cpp

//插入
void Insert(DA * arr,int pose,void * datasPointer)//需要知道数组首地址,插入位置,插入内容
{
    //使用realloc简单完成-扩容
    if(nullptr == arr)
        return;
    if(nullptr == datasPointer)
        return;
    if(pose<=0)
        return;
    if(pose>arr->size)
    {
        pose = arr->size+1;
    }
    //判断空间是否足够
    if(arr->size>=arr->capacity)
    {
        //申请新的空间
        int newcapacity = arr->capacity * 2;//增长策略
        void **newspace =(void **) malloc(sizeof(void *)*newcapacity);//申请更大的内存空间

        //将原来的数据拷贝到新空间
        memcpy(newspace,arr->addr,sizeof(void *)*arr->capacity);

        //释放原来空间
        free(arr->addr);

        //更新addr指向
        arr->addr = newspace;
        arr->capacity = newcapacity;

    }
    //插入
    //移动位置
    qDebug()<<"pose"<<pose;
    for(int i = arr->size-1;i>=pose-1;--i)
    {
        arr->addr[i + 1] = arr->addr[i];
        qDebug()<<i;
    }
    //将新元素插入
    arr->addr[pose-1] = datasPointer;
    ++arr->size;

}

因为是连续储存空间,所以在中间插入,则需要将插入位置后面的元素统一往后移动。 如下图所示:

          

我们先看这段程序:

//插入
//移动位置
if(nullptr == arr)
    return;
if(nullptr == datasPointer)
    return;
if(pose<=0)
    return;
if(pose>arr->size)
{
    pose = arr->size+1;
}

for(int i = arr->size-1;i>=pose-1;--i)
{
    arr->addr[i + 1] = arr->addr[i];
}
//将新元素插入
arr->addr[pose-1] = datasPointer;
++arr->size;

一开始都是对输入参数的甄别过程,如果指针是空,或者输入参数不合格,直接返回,不继续进行。

当要插入的数据大于整个元素个数时?比如目前有5个数据,将新的值插入到第100个位置,没有必要,将这个数据放在整个元素的末尾即可,即第6个位置。

所以有判断,当pose大于size时,pose自动等于size+1; 之后进入插入工作。

i等于最后一个元素的下标,一直循环直到等于要插入元素的下标,及pose-1;

举例说明,一共4个元素,当要在第二个位置插入新元素时,元素4、元素3、元素2往后移动,下标分别是3,2,1,整个循环进行三次。

之后再将新元素插入,新元素下标:pose-1;要查在第二个位置,下标自然就是1.

但是如果内存不够用了怎么办呢?所以在正式插入前要确定一下容量,容量不够,按照要求扩容即可;

//判断空间是否足够
    if(arr->size>=arr->capacity)
    {
        //申请新的空间
        int newcapacity = arr->capacity * 2;//增长策略
        void **newspace =(void **) malloc(sizeof(void *)*newcapacity);//申请更大的内存空间

        //将原来的数据拷贝到新空间
        memcpy(newspace,arr->addr,sizeof(void *)*arr->capacity);

        //释放原来空间
        free(arr->addr);

        //更新addr指向
        arr->addr = newspace;
        arr->capacity = newcapacity;

    }

 遍历

.h

void Foreach(DA * arr,void(*_callback)(void *));//输入参数:需要知道数组首地址,插入位置,插入内容,打印回调函数的函数指针
void myPrint(void * data);

输入参数要求:动态数组结构体类,输出的回调函数

解释: 回调函数在此处出现的意思,因为输入数据的类型,在开发者眼里是未知的,所以输出交给用户去写。

.cpp

void Foreach(DA * arr,void(*_callback)(void *))
{
    if(nullptr == arr)
        return;
    if(nullptr == _callback)
        return;
    for(int i = 0;i<arr->size;++i)
    {
        _callback(arr->addr[i]);
    }
}
void myPrint(void * data)
{
    PS * Print = (PS *)data;
    qDebug()<<"Name:"<<Print->name<<" Age:"<<Print->age;
}

遍历没有什么好说的,从到到尾循环就行了。

删除(两种方式)

1:按位置删除 

.h

//删除
//按照位置删除
void removedata(DA * arr,int pose);//输入参数:需要知道数组首地址,删除元素的位置

 输入参数要求:动态数组结构体类,要删除元素的位置

.cpp

//位置删除
void removedata(DA * arr,int pose)
{
    if(nullptr == arr)
        return;
    if(pose<=0||pose>arr->size)
        return;


//    后面的往前覆盖
    for(int i = pose-1;i<arr->size - 1;++i)
    {
        arr->addr[i] = arr->addr[i+1];
    }
    --arr->size;


}

删除也很简单,直接从后往前覆盖就行。循环起点,要删除节点的下标:pose-1;

停止于最后一个元素的下标size-1;

2:按照数值删除 

.h

//按照值删除
void removedataByvalue(DA * arr,void *data,int(* compare)(void *,void *));//输入参数:需要知道数组首地址,删除元素的指针
int myCpmpare(void *data1,void *data2);

 输入参数要求:动态数组结构体类,要删除元素的地址,回调函数

解释: 回调函数在此处出现的意思,因为输入数据的类型,在开发者眼里是未知的,无法比较大小,所以输出交给用户去写。

.cpp 

按照内容删除 

//内容删除
void removedataByvalue(DA * arr,void *data,int(* compare)(void *,void *))
{
    if(nullptr == arr)
        return;
    if(nullptr == data)
        return;

    if(nullptr == compare)
        return;

    for(int i = 0;i<arr->size;++i)
    {
        if(compare(arr->addr[i],data))//这个部分要根据实际情况来
        {
            removedata(arr,i);
            break;
        }
    }
}
int myCpmpare(void *data1,void *data2)
{
    PS * DataStruct1 = (PS *)data1;
    PS * DataStruct2 = (PS *)data2;

    return ( DataStruct1->name==DataStruct2->name ) && ( DataStruct1->age == DataStruct2->age );
}

销毁

.h

//销毁数组
void Destroy(DA * arr);//输入参数:需要知道数组首地址

 输入参数要求:动态数组结构体类

.cpp 

void Destroy(DA * arr)
{
    if(nullptr == arr)
        return;
    if(arr->addr != nullptr)
    {
        free(arr->addr);
        arr->addr = nullptr;
    }

    free(arr);
    arr = nullptr;

}

释放两次,第一次是二级指针 (addr)指向的连续内存空间

第二次是结构体本身的指针。

测试,下面进行如下输入:

插入元素类型:

typedef struct Person
{
    QString name;
    int age;
}PS;

具体操作: 

DA * Test = Init_dynamicarray(10);
//动态数组添加数据
PS p1 = {"aaa",1};
PS p2 = {"bbb",2};
PS p3 = {"ccc",3};
PS p4 = {"ddd",4};
PS p5 = {"eee",5};
qDebug()<<"开始插入数据";
Insert(Test,1,&p1);//1
Insert(Test,2,&p2);//2
Insert(Test,3,&p3);//3
Insert(Test,4,&p4);//4
Insert(Test,5,&p5);//5
Insert(Test,5,&p1);//6

qDebug()<<"完成插入数据";

qDebug()<<"开始遍历";

Foreach(Test,myPrint);

qDebug()<<"移除元素6和1";
removedata(Test,6);
removedata(Test,1);

qDebug()<<"开始遍历";
Foreach(Test,myPrint);

PS DeleteData = {"ddd",4};
qDebug()<<"移除元素";
removedataByvalue(Test,&DeleteData,myCpmpare);
qDebug()<<"开始遍历";
Foreach(Test,myPrint);

qDebug()<<"销毁动态数组";
Destroy(Test);

插入六个元素,主要第六个元素,是要插在第5个位置,所以将本来在5的eee顶替。

 

附录:

完整版本动态数组

在动态数组中,使用malloc比new更加合适,操作性也更强

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include<QDebug>
namespace Ui {
class MainWindow;
}
typedef struct Person
{
    QString name;
    int age;
}PS;
typedef struct DynamicArray
{
    //数据存储元素的空间首地址
    void **addr;
    //存储数据的内存中最大能够容纳多少元素
    int capacity;//容量
    //当前存储了多少个元素
    int size;
}DA;
//函数设置
//初始化数组
DA * Init_dynamicarray(int capacity);//输入参数:容量大小
//插入元素
void Insert(DA * arr,int pose,void * datasPointer);//输入参数:需要知道数组首地址,插入位置,插入内容
//遍历
void Foreach(DA * arr,void(*_callback)(void *));//输入参数:需要知道数组首地址,插入位置,插入内容,打印回调函数的函数指针
//删除
//按照位置删除
void removedata(DA * arr,int pose);//输入参数:需要知道数组首地址,删除元素的位置
//按照值删除
void removedataByvalue(DA * arr,void *data,int(* compare)(void *,void *));//输入参数:需要知道数组首地址,删除元素的指针
//销毁数组
void Destroy(DA * arr);//输入参数:需要知道数组首地址
//回调函数
void myPrint(void * data);
int myCpmpare(void *data1,void *data2);
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};
void MatrixTest();

.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建动态数组
    DA * Test = Init_dynamicarray(10);
    //动态数组添加数据
    PS p1 = {"aaa",1};
    PS p2 = {"bbb",2};
    PS p3 = {"ccc",3};
    PS p4 = {"ddd",4};
    PS p5 = {"eee",5};

    qDebug()<<"开始插入数据";
    Insert(Test,1,&p1);//1
    Insert(Test,2,&p2);//2
    Insert(Test,3,&p3);//3
    Insert(Test,4,&p4);//4
    Insert(Test,5,&p5);//5
    Insert(Test,5,&p1);//6

    qDebug()<<"完成插入数据";

    qDebug()<<"开始遍历";

    Foreach(Test,myPrint);

    qDebug()<<"移除元素6和1";
    removedata(Test,6);
    removedata(Test,1);

    qDebug()<<"开始遍历";
    Foreach(Test,myPrint);

    PS DeleteData = {"ddd",4};
    qDebug()<<"移除元素";
    removedataByvalue(Test,&DeleteData,myCpmpare);
    qDebug()<<"开始遍历";
    Foreach(Test,myPrint);

    qDebug()<<"销毁动态数组";
    Destroy(Test);
    OWL = new OneWayList(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}
//动态数组
void MatrixTest()
{
    //初始化数据
}
//初始化数组
DA * Init_dynamicarray(int capacity)
{
    if(capacity<=0)
    {
        return nullptr;
    }
    DA * arr = (DA *)malloc(sizeof(DA));
    if(nullptr == arr)
    {
        return nullptr;
    }
    arr->addr = (void **)malloc(sizeof(void *)*capacity );
    arr->capacity = capacity;
    arr->size = 0;

    return arr;
}
//插入元素
void Insert(DA * arr,int pose,void * datasPointer)//需要知道数组首地址,插入位置,插入内容
{
    //使用realloc简单完成-扩容
    if(nullptr == arr)
        return;
    if(nullptr == datasPointer)
        return;
    if(pose<=0)
        return;
    if(pose>arr->size)
    {
        pose = arr->size+1;
    }
    //判断空间是否足够
    if(arr->size>=arr->capacity)
    {
        //申请新的空间
        int newcapacity = arr->capacity * 2;//增长策略
        void **newspace =(void **) malloc(sizeof(void *)*newcapacity);//申请更大的内存空间

        //将原来的数据拷贝到新空间
        memcpy(newspace,arr->addr,sizeof(void *)*arr->capacity);

        //释放原来空间
        free(arr->addr);

        //更新addr指向
        arr->addr = newspace;
        arr->capacity = newcapacity;

    }
    //插入
    //移动位置
    for(int i = arr->size-1;i>=pose-1;--i)
    {
        arr->addr[i + 1] = arr->addr[i];
    }
    //将新元素插入
    arr->addr[pose-1] = datasPointer;
    ++arr->size;

}
//遍历
void Foreach(DA * arr,void(*_callback)(void *))
{
    if(nullptr == arr)
        return;
    if(nullptr == _callback)
        return;
    for(int i = 0;i<arr->size;++i)
    {
        _callback(arr->addr[i]);
    }
}
void myPrint(void * data)
{
    PS * Print = (PS *)data;
    qDebug()<<"Name:"<<Print->name<<" Age:"<<Print->age;
}
//位置删除
void removedata(DA * arr,int pose)
{
    if(nullptr == arr)
        return;
    if(pose<=0||pose>arr->size)
        return;


//    后面的往前覆盖
    for(int i = pose-1;i<arr->size - 1;++i)
    {
        arr->addr[i] = arr->addr[i+1];
    }
    --arr->size;


}
//内容删除
void removedataByvalue(DA * arr,void *data,int(* compare)(void *,void *))
{
    if(nullptr == arr)
        return;
    if(nullptr == data)
        return;

    if(nullptr == compare)
        return;

    for(int i = 0;i<arr->size;++i)
    {
        if(compare(arr->addr[i],data))//这个部分要根据实际情况来
        {
            removedata(arr,i);
            break;
        }
    }
}
int myCpmpare(void *data1,void *data2)
{
    PS * DataStruct1 = (PS *)data1;
    PS * DataStruct2 = (PS *)data2;

    return ( DataStruct1->name==DataStruct2->name ) && ( DataStruct1->age == DataStruct2->age );
}
void Destroy(DA * arr)
{
    if(nullptr == arr)
        return;
    if(arr->addr != nullptr)
    {
        free(arr->addr);
        arr->addr = nullptr;
    }

    free(arr);
    arr = nullptr;

}

 输出:

优缺点:

连续存储空间,直接可以通过位置来访问元素,在访问这个环节就显得非常的简单和方便

但是又是因为是连续的存储空间,对空间储存量要求较高,而且插入元素的过程中非常的麻烦。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值