数据结构与算法(二)向量结构

数组:起始于地址A、物理位置连续的一段存储空间。A[ ]。A[ i]=A+i x s.int *p=A +3,则可以认为P[0]=A[3]。

链接:https://www.nowcoder.com/questionTerminal/22c9e8b544604fffb1698e000a8286a9
来源:牛客网
 

*p+=2;就相当于*p=*p+2;

其中*p指向字符串“123”的第一个元素,即‘1’,指针p向后移两个元素的地址,即指向‘3’

而*(p+2)才是基于p每次向后移一个字符串的长度,即*(p+2)指向“456”

向量的构造:向量的ADT接口,向量的模板类。

向量里面内置了元素类型为T的私有数组,由new申请而成。向量的构造:默认构造,基于复制的构造。

向量的析构:释放用于存放元素的内部数组。向量的动态空间管理:要求向量的实际规模与内部数组容量的比值也称装填因子,不超过一,也不接近于0。可扩充策略:若未满时不扩容,否则加倍,并复制原内容到新数组,释放原来的。在插入时均会调入该算法。在删除操作远多余插入操作,则会减小装填因子。所以,需要在每次删除操作后,进行缩容算法,保证装填因子大于25%。在对单次操作的执行速度及其敏感的应用场合以上策略并不适用。

向量数据结构的构建:抽象数据类型的构建要素:不管是声明还是实现均在同一个头文件下。
1.类的接口声明
2.类接口包含成员变量,成员函数。
成员分为私有成员,公有成员,保护成员。保护成员:关键字protected修饰的成员声明为保护成员,不能被类对象直接访问。其访问权限与私有成员近似,所不同的是其可对于基类的派生类是可见的,而私有成员则不可见。
所以根据情况将变量和函数分为保护,私有和公有三部分。
保护成员变量:规模,存储起始地址,容量。
保护成员函数:初始化,复制向量某一段,扩容,缩容,排序,取最大元素,各种排序。
公有成员:类对象可直接访问的接口。比如:只读,可写,遍历 ,构造等。四种接口。
size(),构造函数,析构函数一般为虚函数,insert(),find(),remove(),disorded(),sort()。
保护成员:成员变量和函数一般都只能在类内访问,无法类对象访问。一般为公有成员所调用。结构一般如下:
template<typename T>class Vector{
protected:
    int _size;T* _elem;
public:
    Vector(int c=DEFAULT_CAPACITY,int s=0,T v=0;){}容量为c,规模为s,默认初始化。s必须小于s。
    vector(T const*a,int n){};数组复制,为何用const。可用a[i]表示元素。
    T& operator[] (int r) const;重载下标操作符,从而引用各元素。为何返回引用?
    vector<T> & opertor=(vector<T> const&);重载赋值操做符。为何必须返回对象的引用呢?
};
实现代码:
template<typename T> vector<T>& vector<T>::operator=(vector<T>const& v){
if(_elem) delete [] _elem;
copyfrom(V._elem,0,V.size());
return *this;
}
1.关于const的使用:一般若函数不改变其传入的参数,则对该参数加const。
T const obj = const T obj。表示该对象为一个常量,不能改变。const T*obj 表明该obj为一个指针,指向一个任意T常量或者变量,但却将其当做常量。也可以写成T const*obj。
T *const obj,一个指针常量,只能指向一个固定的变量obj。T const *const obj 表明一个指针常量可以指向一个变量或常量,但当做常量来看。也可以写成const T*const obj。
常量引用也是类似,T const &obj 等价于const T &obj。均指对常量的引用,初始化时允许引用的对象可以不是常量。const修饰的量确切的说是只读量。
一般若函数不改变成员变量的值,可以在函数后面加const,则会导致该函数无法调用类中的非const成员函数,可以访问所有成员变量不能修改。
数组无法值传递,只能靠指针传递。
2.链式赋值
如何理解函数的返回值?
函数的返回值一般与返回类型相同。若为空返回类型,则return 可当做break退出函数。
值是如何被返回的?返回的值用于初始化调用点的一个临时量,该临时量为函数调用的结果。
返回的值一般为一个未命名的临时对象,再将该临时对象的值对调用点的值进行初始化。

赋值运算符的结果为左侧运算对象,是一个左值。

所以如果返回类型为引用的话,则该对象不能为局部对象。指针也是。可以避免拷贝。引用返回左值。

赋值运算符重载要返回引用以用于类似 (a=b)=c 这样的再次对a=b进行写操作的表达式。所以引用必须。
在面向对象程序设计中,对象间的相互拷贝和赋值是经常进行的操作。
如果对象在申明的同时马上进行的初始化操作,则称之为拷贝运算。例如:
        class1 A("af"); class1 B=A;
    此时其实际调用的是B(A)这样的浅拷贝操作。
    如果对象在申明之后,在进行的赋值运算,我们称之为赋值运算。例如:
        class1 A("af"); class1 B;
        B=A;
        此时实际调用的类的缺省赋值函数B.operator=(A);
关于为何要重载的问题?
int main()
{
A a(1,"herengang");
A b;
}
在程序编译之后,a和b在stack上都被分配相应的内存大小。只不过对象a的域都被初始化,而b则都为随机值。
如果我们执行以下:
    b=a;
则其执行的是缺省定义的缺省的赋值运算。所谓缺省的赋值运算,是指对象中的所有位于stack中的域,进行相应的复制。但是,如果对象有位于heap上的域的话,其不会为拷贝对象分配heap上的空间,而只是指向相同的heap上的同一个地址。因此,对于缺省的赋值运算也就是浅拷贝操作,如果对象域内没有heap上的空间,其不会产生任何问题。但是,如果对象域内有heap上的空间,那么在析构对象的时候,就会连续两次释放heap上的同一块内存区域,从而导致异常。.解决办法--重载(overload)赋值运算符。

随机向量的生成:置乱器,自后向前,将V[i-1]与V[0,i]中的某一元素进行交换。

向量的唯一化处理:o(n*n),若假如向量是有序的,再进行唯一化,可用o(n)的时间。依次用while循环找到不同元素,将其移植到紧邻于前者右侧。发现在唯一化时,向量的大小变了,但是后面的还是有元素存在。若果经过缩容,则不存在。

向量的查找:无序查找,有序查找,二分查找算法。

向量的排序:排序算法的总结,分类:内部排序(内存足够容纳),外部排序(借助外部存储,内存只能容纳一小部分数据。)

离线算法和在线算法,前一情况,待排序的数据以批处理形式整体给出;后一情况,待排序的数据需要实时生成。根据所依赖的体系结构不同有串行和并行两大排序算法。还有根据排序算法是否采用随机策略,来分确定式和随机式之分。

CBA式算法,即比较树算法。

稳定性:考察算法对重复元素的处理。即重复元素的相对次序在排序前后保持一致。

冒泡排序:自左向右,逐一检查各对相邻元素,若逆序,则交换局部有序。稳定算法。

归并排序:两个有序向量,各取出其首元素作比较,小的取出追加至输出向量末尾,该元素的后继成为新的首元素。只需载入两个向量的首元素。主体结构是典型的分治策略。先分治再合并。关键是合并程序。如何写?

二分查找算法:返回值为查找到的目标位置或者比目标数大的第一个数的位置。范围为[begin,end).
适用有重复数字查找。begin=mi+1是为了防止进入死循环。有两种情况。一种是找到结果,一种是没找到。没找到又分为两种情况,一种是接近目标值,一种是等于begin或者是end。
int find(vector&A,int begin,int end,int target){
    if(begin<0 || begin>=end){return -1;}
    int mi=0;
    int max=end;
    while(begin<end){
        mi=begin+end/2;
        if(A[mi]<target) begin=mi+1;
        elseif(A[mi]>target) end=mi;
        else return mi;
    }
    if(begin==max) return -1;在运行时会检测数组是否越界情况。若begin=max,且A[begin]则会引发越界错误,尽管可用begin=max,执行返回,依旧会越界,所以必须严格要求数组不越界。也可证明程序非严格顺序执行。
    return begin;
}
关于二分查找,若范围为[begin,end]呢?可知结果不会有任何改变。依然正确。
merge(Rank lo,Rank mi,Rank hi,T *){
    T* A =elem+lo;
    int lb = mi -lo; T*B =new T[lb];
    for (Rank i =0;i<lb;B[i]=A[i++]);
    int lc = hi-mi;T*c=elem+mi;
    for(Rank i =0,j=0,k=0;(j<lb)||(k<lc);){
        if(j<lb && (!(k<lc) || (B(j)<=c(k))) A[i++]=B[j++];
        if(k<lc && (!(j<lb) || (B(j)<=c(k))) A[i++]=B[j++];
}
delete [] B;
}

时间复杂度:o(nlogn)。稳定算法。

计算数组的逆序对。用到归并排序。
class Solution {
public:
    long long merge(vector<int>&data,vector<int>&copy,int begin,int mid,int end){
        int first=begin,cfirst=begin;
        int second=mid;
        long long res=0;
        while(first<mid && second<end){
            if(data[first]<=data[second]){
                copy[cfirst++]=data[first++];
            }else{
                res=res+mid-first;
                copy[cfirst++]=data[second++];
            }
        }
        for(;first<mid;first++){
            copy[cfirst++]=data[first];
        }
        for(;second<end;second++){
            copy[cfirst++]=data[second];
        }
        for(int i=begin;i<end;i++){
            data[i]=copy[i];
        }
        return res;
    }
   long long guibingSort(vector<int>&data,vector<int>&copy,int begin,int end){
         if(begin==end-1)
          {
            return 0;
          }
        int mid=(begin+end)/2;
        long long left=0;
        long long right=0;
        long long count=0;
        left=guibingSort(data,copy,begin,mid);
        right=guibingSort(data,copy,mid,end);
        count=merge(data,copy,begin,mid,end);
       return left+right+count;
    }
    int InversePairs(vector<int> data) {
        long long res=0;
        int begin=0;
        int end=data.size();
        vector<int>copy(data);
        res=guibingSort(data,copy,begin,end);
        return res%1000000007;
        
    }
};
在计算逆序时出错。也就是在融合时计算逆序出错。归并排序属于明显的伪递归,所以可以转化为迭代的方式计算。
向量的具体操作接口:
构造:
std::vector<double> values;没有元素,没有分配空间。
std::vector<double> values(20);20个元素值为0
std::vector<double> values(20,1);20个元素值为一。
std:vector<string> words {"one", "two","three", "four", "five"};列表初始化。
std::vector<std::string> words_copy {std::begin(words) , std::end(words)};输入迭代器。数组列表均可,但是所指向的对象必须为string。[first,last).
std::vector<int> fourth (third); 另一个vector对象。

获取访问元素:
values.front();
values.back();
values.push_back();
values.pop_back();
values.emplace_back();
auto iter = words.insert(++std::begin(words), "two");返回的迭代器指向被插入的元素 string(”two”)
auto riter = std::find(std::rbegin(str), std::rend(str) , "one");
data.clear();
auto iter = data.erase(std::begin(data)+1); 
auto iter = data.erase(std::begin(data)+1,std::begin(data)+3);
std::swap(std::begin(data)+1,std::end(data)-1);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值