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

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

  • 在排序数组中找出给定数字出现的次数
  • 计算两个有序整型数组的交集
  • 如何找出数组中重复次数最多的数
  • 在On的时间复杂度内找出数组中出现次数超过了一半的数
  • 找出数组中唯一的重复元素
    • 解题方法
    • 引申一没有要求每个数组只访问一次不能用辅助存储空间
    • 引申二

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

在排序数组中,找出给定数字出现的次数。如,【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]]是一个重复数

时间: 2024-11-05 18:53:00

数据结构与算法:数组(一)的相关文章

Java数据结构与算法-数组

1 public class TestMyArray{ 2 3 public static void main(String[] args) { 4 5 MyArray array = new MyArray(6); 6 array.insert(10); 7 array.insert(12); 8 array.insert(8); 9 array.insert(17); 10 array.insert(6); 11 array.insert(15); 12 array.display(); 1

数据结构与算法 - 数组

题型1:如何用递归实现数组求和 方法1: 题型2:如何用一个for循环打印一个二维数组 方法1:array在二维数组中的行号和列号分别为[i/MAXY],[i%MAXY] 题型3:用递归和非递归的方法实现二分查找 题型4:如何在排序数组中,找出给定数字出现的次数 方法1:二分查找,分别找出左边界和右边界,左右边界的差 方法2:顺序查找,统计计数 题型5:如何计算两个有序整型数组的交集 方法1:采用二路归并来遍历两个数组,分别用i.j从头开始遍历两个数组,如果arr[i]<arr[j],i++:如

数据结构与算法系列研究四——数组和广义表

稀疏矩阵的十字链表实现和转置 一.数组和广义表的定义 数组的定义1:一个 N 维数组是受 N 组线性关系约束的线性表.           二维数组的逻辑结构可形式地描述为:           2_ARRAY(D,R)              其中 D={aij} | i=0,1,...,b1-1; j=0,1,...,b2-1;aij∈D0}              R={Row,Col}              Row={<aij,ai,j+1>|0<=i<=b1-1;

《数据结构、算法与应用》8.(顺序查找数组中第一个出现指定元素的位置)

最近在读<数据结构.算法与应用>这本书,把书上的习题总结一下,用自己的方法来实现了这些题,可能在效率,编码等方面存在着很多的问题,也可能是错误的实现,如果大家在看这本书的时候有更优更好的方法来实现,还请大家多多留言交流多多指正,谢谢 8. 从左至右检查数组a[0:n-1]中的元素,以查找雨x相等的那些元素.如果找到一个元素与x相等,则函数返回x第一次出现所在的位置.如果在数组中没有找到这样的元素,函数则返回-1. // // main.cpp // Test_08 // // Created

Java数据结构和算法(二)——数组

数组的用处是什么呢?--当你需要将30个数进行大小排列的时候,用数组这样的数据结构存储是个很好的选择,当你是一个班的班主任的时候,每次要记录那些学生的缺勤次数的时候,数组也是很有用.数组可以进行插入,删除,查找等. 1)创建和内存分配 Java中有两种数据类型,基本类型和对象类型,也有人称为引用类型,Java中把数组当成对象,创建数组时使用new操作符. int array[] = new int[10]; 既然是对象,那么array便是数组的一个引用,根据Java编程思想(一) -- 一切都是

Java数据结构和算法之数组与简单排序

一.数组于简单排序 数组 数组(array)是相同类型变量的集合,可以使用共同的名字引用它.数组可被定义为任何类型,可以是一维或多维.数组中的一个特别要素是通过下标来访问它.数组提供了一种将有联系的信息分组的便利方法. 一维数组 一维数组(one‐dimensional array )实质上是相同类型变量列表.要创建一个数组,你必须首先定义数组变量所需的类型.通用的一维数组的声明格式是: type var‐name[ ]; 获得一个数组需要2步: 第一步,你必须定义变量所需的类型. 第二步,你必

数据结构与算法javascript描述笔记--数组篇

数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数.然而,这些数字索引在内部被转换为字符串类型,这是因为 JavaScript 对象中的属性名必须是字符串.在内部被归类为数组.由于 Array 在 JavaScript 中被当作对象,因此它有许多属性和方法可以在编程时使用. 使用数组: 1.创建数组 ① 使用 [] 操作符 ,var arr=[] ,该方法效率最高. ② 调用 Array 的构造函数创建数组,var myArr=new

【算法与数据结构】图 -- 数组表示法

图的数组表示法 借助一个二维数组表示图,该二维数组的第i行,第j列的值表示从Node[i]到Node[j]: 无向图(网):是否有边 / 权值,arr[i][j] == arr[j][i],无向图(网)的特性,矩阵关于对角线对称. 有向图(网):是否有弧 / 权值. //图的数组表示法 //最大顶点个数 const int MAX_VERTEX = 100; //最大值 const int MAX_VALUE = (1 << 31) - 1; typedef struct _tagArcCel

翻阅《数据结构与算法javascript描述》--数组篇

导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数.然而,这些数字索引在内部被转换为字符串类型,这是因为 JavaScript 对象中的属性名必须是字符串.在内部被归类为数组.由于 Array 在 JavaScript 中被当作对象,因此它有许多属性和方法可以在编程时使用. 使用数组: 1.创建数组 使用 [] 操作符 ,var a

java常用的数组、字符串、集合操作以及数据结构与算法基本知识

java中常用封装的数组 .字符串. 集合来操作对象,但数据结构中常用的有栈和队列   数与图以及他们之间的排序,查找. 数组声明没有分配内存空间  只有创建或者是初始化时才分配,创建时把数组中的数据类型数据所在的内存空间首地址赋值给数组名,在创建每个对象时,都会给该对象分配地址和它存储的数据 .如变量    int arr[]; int arr[]={1,2,3};  arr=new int[10] ,int arr[][]={{1,9,7},{1,2,3}}  int arr[][]=new