数据结构与算法:数组(一)

原创 2016年06月02日 08:29:28

数据结构与算法是计算机发展的基石,现代计算机的起源是数学,数学的核心是算法,计算机历史上每一次大的变革都离不开算法的推动。纵然“条条大路通罗马”,但好的算法永远比提高硬件设备管用。

在排序数组中,找出给定数字出现的次数

在排序数组中,找出给定数字出现的次数。如,【1,2,2,2,3】中2出现的次数是3次。。。

简单粗暴的方法是从头到尾遍历一遍,用个计数器进行计数,统计2出现的次数。

优化方法:该问题在二分查找的基础上进行改进。设数组array为递增序列,需要查找的元素为findData,为了求解给定数字出现的次数,可以分别寻找findData在array中最先出现的位置和最后出现的位置,通过两者的算术运算即可获得该数字的出现次数。编码的时候,用变量last来存储本次查找到的位置,然后根据情况变换查找方向,就可以分别确定最先出现的位置的下标left和最后出现的位置下标right的值。

#include<iostream>

using namespace std;

int BinarySearch( int *a, int length, int num, bool isLeft )
{

    int left = 0;
    int right = length - 1 ;
    int last = 0;

    while( left <= right )
    {
        int mid = ( left + right ) /2;
        if( a[mid] < num )
        {
            left = mid+1;
        }
        else if( a[mid] > num )
        {
            right = mid - 1;

        }
        else 
        {
            last = mid;
            if( isLeft ) right = mid-1;
            else left = mid + 1;
        }

    }
    return last>0 ? last : -1;
}

int main()
{

    int data[] = { 0,1,2,3,3,3,3,3,3,4,5,6,7,13,19 };

    int lower = BinarySearch( data,sizeof(data)/sizeof(data[0]),3,true );
    int upper = BinarySearch( data,sizeof(data)/sizeof(data[0]),3,false);

    int count = upper - lower + 1;
    cout << count << endl;
    return 0;
}

计算两个有序整型数组的交集

如,两个含有n个元素的有序(非降序)整型数组a和b(数组a和b中都没有重复元素),求出其共同元素。
a = 0 , 1 , 2, 3, 4
b = 1, 3, 5, 7, 9
交集为{ 1,3 }。

计算数组交集可以采用很多种方法,但数组的相对大小一般会影响算法的效率,所以需要根据两个数组的大小来确定采用的方法。。

(1)对于两个数组长度相当的情况,一般可以采取以下3种方法。

  • 采用二路归并来遍历两个数组

    设两个数组分别为array1[n1] 和 array2[n2],分别以i、j从头开始遍历两个数组。在遍历过程中,如果当前遍历位置的array1[i]和array[j]相等,则此数为两个数组的交集,记录下来,并继续向后遍历array1和array2。如果array1[i] 大于 array2[j] ,则需继续向后遍历array2. 如果array1[i] 小于 array2[j],则需继续向后遍历array1,直到有一个数组结束遍历即停止。

  • 顺序遍历两个数组,将数组元素存放到哈希表中,同时对统计的数组元素进行计数。如果为2,则为两者的交集元素。

  • 遍历两个数组中的任意一个数组,将遍历得到的元素存放到哈希表,然后遍历另外一个数组,同时对建立的哈希表进行查询,如果存在,则为交集元素。

(2)对于两个数组长度相差悬殊的情况,如果数组a的长度远远大于数组b的长度,则可以采用下面几种方法。

  • 依次遍历长度小的数组,将遍历的得到的数组元素在长数组中进行二分查找。具体而言,设两个指向两个数组末尾元素的指针,取较小的那个数在另一个数组中二分查找,找到,则存在一个交集,并且将该目标数组的指针指向该位置的前一个位置。如果没有找到,同样可以找到一个位置,使得目标数组中在该位置后的数肯定不在另一个数组中存在,直接移动该目标数组的指针指向该位置的前一个位置,再循环找,直到一个数组为空为止。因为两个数组中都可能出现重复的数,因此二分查找时,当找到一个相同的数x时,其下标为i,那么下一个二分查找的下界变为i+1,避免x重复使用。

  • 采用与方法一类似的方法,但是每次查找在前一次查找的基础上进行,这样可以大大缩小查找表的长度。

  • 采用与方法二类似的方法,但是遍历长度小的数组的方式有所不同,从数组头部和尾部同时开始遍历,这样可以进一步缩小查找表的长度。

如何找出数组中重复次数最多的数

例如,数组 { 1,1,2,2,4,4,4,4,5,5,6,6,6,},元素1出现的次数为两次,元素2出现的次数为2次,元素4出现的次数为4次,元素5出现的次数为两次,元素6出现的次数为3此,问题就是要找出出现重复次数最多的数,所以输出应该为元素4.可以采用如下的两种方法来计算数组中重复次数最多的数。

(1)以空间换时间,定义一个数组int count[MAX],并将其数组元素都初始化为0,然后执行for( int i=0;i<100;i++ ) count[A[i]]++;操作,在count中找最大的数,即为重复次数最多的数。

(1)是一种典型的空间换时间的算法。一般情况下,除非内存空间足够大,否则一般不采用这种方法。

(2)使用map映射表,通过引入map表(map是STL的一个关联容器,它提供一对一的数据处理能力,其中第一个为关键字,每个关键字只能在map中出现一次,第二个称为该关键字的值)来记录每一个元素出现的次数,然后判断次数大小,进而找出重复次数最多的元素。

#include<iostream>
#include<map>

using namespace std;

bool findMostFrequentInArray( int *a, int size, int &val )
{

    if(size==0)
        return false;
    map<int, int> m;
    for(int i = 0; i<size; i++ )
    {
        if( ++m[a[i]]>=m[val] )
            val = a[i];
    }


    return true;
}

int main()
{

    int a[] = { 1,2,3,4,4,4,5,5,5,5,6 };
    int val = 0;

    if( findMostFrequentInArray( a,11,val ) )
        cout << val << endl;

    return 0;
}

在O(n)的时间复杂度内找出数组中出现次数超过了一半的数

如果本题对时间复杂度没有要求,可以采用很多方法。

  • 第一种方法是建立一个二维数组,一维存储数组中的数据,二维存这个数出现的次数,出现次数最多的那个数就是要找的那个数。由于某个数出现的次数超过数组长度的一半,所以二维数组的长度只需要这个数组的一半即可,但是这种方法的时间复杂度和空间复杂度都比较大。

  • 第二种方法是先对数组排序,然后取中间元素即可,因为如果某个元素的个数超过一半,那么数组排序后该元素必定占据数组的中间位置。如果出现最多的那个数是最小的,那么1—(n+1)/2都是那个数;如果出现最多的那个数是最大的,那么(n-1)/2-n都是那个数;如果不是最小也不是最大,当这个数由最小慢慢变成最大的数时,会发现中间的那个数的值是不变的,所以中间那个数就是要找的那个数。时间复杂度就是排序用的时间,即最快的排序算法的时间复杂度O(nlogn)。

但由于本题对时间复杂度有要求,以上方法显然都达不到要求,不可取,需要采取非常规方法,利用一些其他技巧来实现。

  • 每次去除两个不同的数,剩下的数字中重复出现的数字肯定比其他数字多,将规模缩小化。如果每次删除两个不同的数(不管包不包括最高频数),那么在剩余的数字里,原高频数出现的频率一样超过了50%,不断重复这个过程,最后剩下的将全市同样的数字,即最高频数。此算法避免了排序,时间复杂度只有O(n)。
#include<iostream>

using namespace std;

int findMostApperse( int *num, int len )
{
    int candidate = 0;
    int count = 0;
    for( int i=0; i<len; i++ )
    {
        if( count==0 )
        {
            candidate = num[i];
            count = 1;
        }
        else
        {
            if( candidate == num[i] )
                count++;
            else
                count--;
        }
    }
    return candidate;
}

int main()
{

    int arr[] = { 2,1,1,2,3,1,1,1 };
    int len = sizeof(arr)/sizeof(arr[0]);
    cout << findMostApperse( arr,len ) << endl;
    return 0;
}
  • Hash 法。首先创建一个hash_map,其中key为数组元素值,value为此数出现的次数。遍历一遍数组,用hash_map统计每个数出现的次数,并用两个值存储目前出现次数最多的数和对应出现的次数,此时的时间复杂度为O(n),空间复杂度为O(n),满足题目要求。
  • 使用两个变量A和B,其中变量A存储某个数组中的数,变量B用来计数。开始时将变量B初始化为0,遍历数组:

    如果当前数与A不同,则需要分两种情况进行讨论
    (1)如果B等于0,则令A等于当前数,令B等于1。
    (2)如果B大于0,则令B=B-1。
    如果当前数与A相同,则令B=B+1。遍历结束时,A中存储的数就是所要找的数。这个算法的时间复杂度是O(n),空间复杂度是O(1)

#include<iostream>

using namespace std;

int main()
{
    int i,A,B;
    int a[10] = { 1,2,3,1,2,1,1,6,1,1 };
    A = a[5];
    B = 0;

    for( i=0;i<10; i++ )
    {
        if( B==0 )
        {
            A = a[i];
            B = 1;
        }
        else if( A== a[i] )
        {
            B++;
        }
        else if( A!=a[i] )
        {
            B--;
        }
    }

    cout << A <<endl;

    return 0;
}

找出数组中唯一的重复元素

数组a[N], 1至N-1这N-1个数存放在a[N]中,其中某个数重复一次,写一个函数,找出被重复的数字。要求每个数组元素只能访问一次,不能用辅助存储空间

解题方法

由于题目要求每个数组元素只能访问一次,不用辅助存储空间,可以从原理上入手,采用数学求和法,因为只有一个数字重复一次,而数又是连续的,根据累加和原理,对数组的所有项求和,然后减去1至N-1的和,即为所求的重复数。

引申一:没有要求每个数组只访问一次,不能用辅助存储空间

如果题目没有要求每个数组元素只能访问一次,不用辅助存储空间,还可以用异或法和位图法来求解。

  • 异或法

    根据异或法的计算方式,每两个相异的数执行异或运算之后,结果为1;每两个相同的数异或之后,结果为0,所以数组a[N]中的N个数异或结果与1至N-1异或的结果再做异或,得到的值即为所求。

    用异或巧妙的解决对比问题

  • 位图法

位图法的原理是首先申请一个长度为N-1且均为‘0’组成的字符串,然后从头开始遍历数组a[N],取每个数组元素a[i]的值,将其对应的字符串中的相应位置置1,如果已经置1,那么该数就是重复的数。由于采用的是位图法,所以空间复杂度比较大,为O(n)。

引申二

此题进行一个变形:取值为【1,n-1】含n个元素的整数数组,至少存在一个重复数,即可能存在多个重复数,O(n)时间内找出其中任意一个重复数。例如,array【】= { 1,2,2,4,5,4},则2和4均是重复元素。

  • 位图法。使用大小为N位图,记录每个元素是否出现过,一旦遇到一个已经出现过的元素,则直接输出。时间复杂度是O(n),空间复杂度是O(n)

  • 数组排序法。首先对数组进行计数排序,然后顺次扫描整个数组,直到遇到一个已出现的元素,直接将之输出。时间复杂度为O(n),空间复杂度为O(n)

  • Hash法

    以上的两种方案都需要额外的存储空间,能不能不使用额外的存储空间?。。数组元素如果是有符号的int型,则本方法可行。将数组元素作为索引,对于元素array[i],如果array[array[i]]大于0,则设置 array[array[i]] = -array[array[i]];如果array[array[i]]小于0,则array[array[i]]是一个重复数

这里写图片描述

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

c语言版数据结构(奇迹冬瓜)-数组和广义表(稀疏矩阵的转置算法一)

//稀疏矩阵 /* |0 12 9 0 0 0 0| |0 0 -3 0 0 15| |0 0 0 0 0 0 0|...

《java数据结构与算法》——数组

一直琢磨着要好好学数据结构与算法,之前也零星地看了点,但是总是不成体系,我在网上也找了很多这方面的书,但是作为一个初学者,总希望能找到一本适合入门但是又比较实用的书,最后千挑万选地邂逅了《java数据...

再回首,数据结构——字符串与数组的常见操作(链式存储,包含朴素匹配算法等)

最近在复习数据结构,顺便看看大一的时候写的代码,看完之后比当初有了更加深刻的体会。          希望这些能提供给初学者一些参考。 //鏈式存儲的數據結構 typedef struc...

javascript数据结构和算法 第二章 (数组)

数组是计算机编程中最为常见的数据结构.每一种编程语言都包括某种形式的数组.因为它们是内置的所以它们非常高效,往往被用来作为存储数据的选择. 这一章,我们将会研究数组在javascript中是如何工作...

Java数据结构与算法之数组排序——冒泡

冒泡排序很简单,根据图形来记算法,非常容易。
  • a80C51
  • a80C51
  • 2015年09月29日 23:14
  • 380

数据结构与算法JavaScript - 数组

数组的标准定义是:一个存储元素的线性集合,可以通过索引来任意存取,是一种特殊的对象 在js中创建数组的方式有很多种 // 使用[]操作符声明数组 var nums = []; // 使用Array构造...

Java 数据结构和算法 数组

数组 数组是应用最广泛的数据结构。它被植入到大部分编程语言中,由于数组十分易懂,所以作为数据结构的起点,并展示面向对象编程和数据结构之间的关系。 Java中数组的基础知识这里就不做赘述。 二...

《数据结构、算法与应用》7.(动态调整数组大小)

最近在读《数据结构、算法与应用》这本书,把书上的习题总结一下,用自己的方法来实现了这些题,可能在效率,编码等方面存在着很多的问题,也可能是错误的实现,如果大家在看这本书的时候有更优更好的方法来实现,还...
  • oktears
  • oktears
  • 2014年04月13日 21:31
  • 1265
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:数据结构与算法:数组(一)
举报原因:
原因补充:

(最多只允许输入30个字)