前言:浅谈动态数组
在C++与其他高级语言中,本就存在着静态数组这一线性数据结构。静态数组是具有固定元素个数的群体,每个元素都可通过下标索引直接访问。
尽管静态数组是十分重要的数据结构,但也存在缺陷,因为其大小在编译时就已经确定,故在运行时无法修改。
最关键的是,因为静态数组会因为数组越界问题带来重大安全隐患,故动态数组在某些应用场景下就显得尤为重要了。
文章目录
一、dynamic Array代码实现
//此文件名为Array
#include <cassert>
template<class T>
class Array{
public:
Array(long long sz=100); // constructor
Array(const Array<T> &a); //copy_constructor
~Array(); //destructor
Array<T>& operator = (const Array<T> &array); //重载运算符 = 实现赋值操作
T &operator [] (long long i); //重载索引运算符 [] 实现下标索引
const T &operator [] (long long i) const;
operator T*(); //重载“星星运算符 ”让程序更漂亮 ^&^
operator const T*() const;
long long get_size() const; //获取私有成员size外部接口
void resize(long long sz); //更改私有成员size外部接口
private:
T* list; //T类型指针,为动态数组首地址
long long size; //数组大小,这儿也可以定义一个常量宏,每次更改#define size number即可
};
//构造函数
template<class T>
Array<T>::Array(long long sz){
assert(sz>=0); //judge size,其大小应该为非负数
size=sz;
list=new T[size]; //动态分配size个T型内存空间,还可以使用<cstdlib>头文件下的malloc函数
}
//析构函数
template<class T>
Array<T>::~Array(){
delete [] list; //注意不同于释放单个内存的方式,malloc函数对应为 free关键字
}
//复制构造函数
template<class T>
Array<T>::Array(const Array<T> &a){
size=a.size; //从右值对象a中取得size大小
list=new T[size];
for(long long i=0;i<size;i++) //逐个赋值
list[i]=a.list[i];
}
//重载运算符“=”
template<class T>
Array<T>& Array<T>::operator =(const Array<T> &array){
if(array!=this) //若本对象数组大小与array不一,则删除本对象并重新分配内存空间!
{
if(size!=array.size)
{
delete [] list;
size=array.size;
list=new T[size];
}
//若等大,则直接进行赋值操作
for(long long i=0;i<size;i++){
list[i]=array.lsit[i];
}
}
return *this; //在数组很大的情况下,返回本对象的引用避免了临时变量的创建,节省了时间特别是空间!
}
//重载下标运算符[]
template<class T>
T & Array<T>::operator [](long long n){
assert(0<=n&&n<=size);
return list[n];//注意,返回值为单个T类型数据
}
//重载const版本
template<class T>
const T & Array<T>::operator [](long long n)const{
assert(0<=n&&n<=size);
return list[n];
}
//重载星星运算符,将array类的对象名转化为T类型的指针!
template <class T>
Array<T>::operator T*(){
return list;
}
//const版本
template <class T>
Array<T>::operator const T*()const{
return list;
}
//获取数组大小
template <class T>
long long Array<T>::get_size() const{
return size;
}
//更改数组大小,大家自行分析一下语句作用,大部分操作上文已实现。
template <class T>
void Array<T>::resize(long long sz){
assert(sz>=0);
if(sz==this->size)
return ;
T *newlist=new T[sz];
long long n=sz<size?sz:size;
size=sz;
for(long long i=0;i<n;i++)
newlist[i]=list[i];
delete [] list;
list=newlist;
}
int main(){
return 0;
}
二、有关IS_Prime的算法
Prime(质数)是个啥家伙?
百度她说啊:
“质数又叫素数,是在大于1的自然数中,除1和其本身以外没有其他因数的自然数。例:7只能被1、7整除,不能被其他数字整除,那么7就是质数。质数有:2、3、5、7、11、13、17等。其中,2是最小的质数,也是唯一的偶数质数。
引入:
现在来做道大家都见识过的初级编程题:
请实现一个IS_Prime函数,对于给定的整型参数 N>=1,该函数将把自然数中,小于 N 的质数,从小到大打印出来。
如,当 N = 10,则打印出:
2 3 5 7
常规法:
即简单暴力枚举法,从2开始(因1非质),设置循环变量N,通过试除法(这里不多赘述,就时根据质数定义来的,若未了解,建议大家自己给模拟出来),逐个判断其是否为质数,因为是逐个,所以2~N-1间每个数都需要判断,若符合质数判断函数,则将其存下 。
bool isPrime(int n){
bool flag=true;
for(int i = 2; i < n; i++){
if (n % i == 0) {
flag=false;
break;
}
}
return flag;
}
//main函数如下:
int main(){
int n=100;
for(int i=2;i<=n;i++)
{
if(isPrime(i))
cout<<i<<endl;
}
return 0;
}
//写出main函数的意义是为了告诉大家在main中还有一个for循环,
//所以时间复杂度将近n的平方;
常规法改进:
我们在现有算法一的基础上进行改进~
在枚举过程中,如果能找准枚举变量或者减少问题规模,那么就是更好的算法!
由质数的定义还可以知道,偶数一定为非质数。所以,可将循环边界除以二,那么算法的平均复杂度将提高一倍!
我们再稍微聪明一点!除了2以外,所有的质因数都是奇数。所以,我们就先尝试 2,然后再尝试从 3 开始一直到 N/2 的所有奇数。
嘿嘿,或许大家也知道:其实只要从 2 一直尝试到√N,就可以了。估计有些网友想不通了,为什么只要到√N 即可?
简单d释一下:因数都是成对出现的。比如,100的因数有:1和100,2和50,4和25,5和20,10和10。看出来没有?成对的因数,其中一个必然小于等于100的开平方,另一个大于等于100的开平方。我们只要找到N的一对因数,那么其就为非质数了,所以问题转化为寻找N的较小因数,而它的范围在2~√N 之间。
那么,就有小伙伴一下子能想到,寻找2~√N之间的奇数即可。
所以到目前为止,最优算法如下:
bool isPrime(int n){
bool flag=true;
for(int i = 2; i < sqrt(n); i++){
if (n%2==0||n%i==0) {
flag=false;
break;
}
}
return flag;
}//注意,此时main函数仍同上
可是呐,学无止境,下面给大家介绍一种时间复杂度接近n的算法:
埃拉托斯特尼筛法!
埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
首先,2是公认最小的质数,所以,先把所有2的倍数去掉;然后剩下的那些大于2的数里面,最小的是3,所以3也是质数;然后把所有3的倍数都去掉,剩下的那些大于3的数里面,最小的是5,所以5也是质数……
上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。维基百科上有一张很形象的动画,能直观地体现出筛法的工作过程。
三、动态数组模板实例化实现埃氏算法
//注意,需包含实现动态数组模板的文件Arrady
#include <iostream>
#include <iomanip>
#include "Array.h"
using namespace std;
int main(){
Array<long long> a(10);//用于存放质数的动态数组
long long cnt=0;
long long n;
cout<<" Please Enter a value :";
cin>>n;
for(long long i=2;i<=n;i++){
bool isPrime=1;
for(long long j=0;j<cnt;j++)
if(i%a[j]==0)
{
isPrime=false;
break;
}
//把i写入质数数组
if(isPrime)
{
//如果数组满了,则动态加倍
if(cnt==a.get_size())
a.resize(cnt*2);
a[cnt++]=i;
}
}
//输出质数
for(long long i=0;i<cnt;i++)
cout<<setw(8)<<a[i]<<endl;//setw()为字宽
return 0;
}
打印百万以内的质数,注意,只跑了48.14seconds!
而使用前面几种算法,估计没吃个瓜的时间,是看不到它的打印结果咯…
四、总结
关于C++模板的知识,这也只是冰山一角,不过却也可以使用它来解决一些问题,如实现这动态数组,乃至其他数据结构。
而有关质数的算法,以上算法为一个循序渐进的改进过程,最终可以在时间复杂度上达到一个不错的效果。
正所谓:山外青山楼外楼,算法怎能就此休?除埃氏筛法外,还有着线性筛法等超乎想象的高效率算法!并且,此文章并未就空间复杂度改进算法。
下面给出两篇参考博客,以飨读者:
线性筛法
质数高效算法时间、空间复杂度的逐步求精
如果大家觉得有所收获还请“飞鸿踏雪”,还请各位大佬指点评价!