目录
1数据结构理论
1.1数据结构概念
数据结构是计算机存储,组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高校的检索算法和索引技术有关。
数据结构是计算机存储,组织数据的方式。
1.2算法的概念
算法是特定问题求解步骤的描述,在计算机中表现为指令的有限序列,算法是独立存在的一种解决问题的方法和思想。
对于算法而言,语言并不重要,重要的是思想。
2线性表
2.1线性表基本概念
线性结构是一种最简单且常用的数据结构。线性结构的基本特点是节点之间满足线性关系。
常见的,动态数组、链表、栈、队都属于线性结构。
他们的共同之处,是节点中有且只有一个开始节点和终端节点。
按照这种关系,可以把它们的所有节点排列成一个线性序列。
但是,他们分别属于几种不同的抽象数据类型实现,它们之间的区别,主要是操作的不同。
线性表是零个或者多个数据元素的有限序列,数据元素之间是有顺序的,数据元素个数是有限的,数据元素的类型必须相同
用连续的空间储存——数组
用非连续的空间储存——链表
2.2线性表的顺序储存
- 需要知道存储数据元素空间的首地址
- 元素的个数
- 数组的总容量(不够的时候需要扩容,所以需要知道容量)
- 数据类型(void *)
只存储数据的地址,不存成具体的内容,只是为了将数据整合。
2.2.1线性表顺序储存(动态数组)的设计和实现
整体设计思路
动态数组的特点:
- 连续内存
- 储存的是void *类型变量
- 二级万能指针(void **)指向这段内存的首地址
整个数据结构,需要一个指向这段连续内存的指针,这样才能访问这段内存,及void **
需要知道已经放入元素的个数和这段连续内存的大小(不时之需可能要扩容)
这三个要素构成了动态数组
动态数组的组成:
- 指向连续内存的首地址
- 连续内存容积
- 存储内容个数
初始化
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;
}
输出:
优缺点:
连续存储空间,直接可以通过位置来访问元素,在访问这个环节就显得非常的简单和方便
但是又是因为是连续的存储空间,对空间储存量要求较高,而且插入元素的过程中非常的麻烦。