二維數組中查找某數字,引發的二維數組與二級指針問題

《劍指offer》中面試題4:

問題描述: 
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。 
1 2 8 9

2 4 9 12

4 7 10 13

6 8 11 15

分析: 
首先我們選取數組右上角的數字9.由於9大於7,並且9還是第4列的第一個(也是最小的)數字,因此7不可能出現在數字9所在的列,於是只需要分析剩下的3列,位於右上角的數字是8.同樣8大於7,因此8所在的列也可以剔除。接下來只需分析剩下的兩列,由於2小於7,那麼要查找的7可能在2的右邊和下邊。由於2的右邊都已經被剔除,所以只可能出現在2的下邊,將2所在的行剔除,只剩下三行兩列,再和前面同樣的方法查找,當找到7時就結束了。 
總結規律如下:首先選取矩陣右上角的數字。如果等於要查找的數字,查找過程結束;如果大於要查找的數字,則剔除這個數字所在的列,如果小於要查找的數字,則剔除這個數字所在的行。這樣每一步都可以縮減查找範圍,直到找到要查找的數字,或者查找失敗。

書上主代碼如下:

bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;

    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns-1;
        while (row < rows && column >= 0)
        {
            if(matrix[row*columns + column] == number)
            {
                found = true;
                break;
            }
            else if(matrix[row*columns + column] > number){
                --column;
            }
            else {
                ++row;
            }
        }
    }
    return found;
}

 現在要爲其編寫測試代碼,代碼中需要傳入二維數組,二維數組在內存中佔據連續的空間,實際上可以當成是連續的一維數組,二維數組的首地址做參數時會弱化成int*,這樣在Find函數體內要按二維數組的行列思路讀取值時,要通過row*colums+column的方式讀取。

那麼如果我想在函數體內直接按p[i][j]的方式直觀讀取呢?!  我想到傳入int**,(這種方式並不推薦,僅做測試,搞明白二維數組與二級指針間的區別),原以爲直接將int matrix[][4]首地址強制轉換成int**就行,結果是錯誤的,只能強制轉換成int*,原因上面已經說了。int**是一個二級指針,指向int*的指針,內存中分配的地址是不連續的。若非用這種方式轉換,只能如下賦值:

void Test3()
{
    int p[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    //二維數組實際只是一個指向int的指針,而int**是二級指針,完全不等價。不能強制轉換
 //   int **matrix;
 //   matrix = new int*[4]; //row, 或者如下寫法
    int* matrix[4]; //row,指向指針int*的數組,分配的內存不連續,
                    //下面TestTwoDimention中做參數時自動弱化成int**指針
    for (int i=0; i<4; i++) {
        matrix[i] = new int[4]; //column
    }
    for (int i=0; i<4; i++) {
        for (int j=0; j<4; j++) {
            matrix[i][j] = p[i][j];
        }
    }

    TestTwoDimention("Test3", matrix, 4, 4, 1);
}

這樣就能傳入int**參數,在函數體內直接matrix[i][j]讀取。

//若要函數體內能直觀p[i][j]取值,參數爲int**,傳的是二級指針,並非二維數組,見Test3
bool FindTwoDimention(int** matrix, int rows, int columns, int number)
{
    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns-1;
        while (row < rows && column >= 0) {
            if(matrix[row][column] == number)
                return true;
            else if (matrix[row][column] > number) {
                --column;
            }
            else {
                ++row;
            }
        }
    }
    return false;
}

 

<c程序設計語言>中的關於這個的解釋:

Newcomers to C are sometimes confused about the difference between a two-dimensional array and an array of pointers, such as name in the example above. Given the definitions 

   int a[10][20];

   int *b[10];

then a[3][4] and b[3][4] are both syntactically legal references to a single int. But a is a true two-dimensional array: 200 int-sized locations have been set aside, and the conventional rectangular subscript calculation 20 * row +col is used to find the element a[row,col]. For b, however, the definition only allocates 10 pointers and does not initialize them; initialization must be done explicitly, either statically or with code. Assuming that each element of b does point to a twenty-element array, then there will be 200 ints set aside, plus ten cells for the pointers. The important advantage of the pointer array is that the rows of the array may be of different lengths. That is, each element of b need not point to a twenty-element vector; some may point to two elements, some to fifty, and some to none at all. 

Although we have phrased this discussion in terms of integers, by far the most frequent use of arrays of pointers is to store character strings of diverse lengths, as in the function month_name. Compare the declaration and picture for an array of pointers: 

   char *name[] = { "Illegal month", "Jan", "Feb", "Mar" };

with those for a two-dimensional array: 

   char aname[][15] = { "Illegal month", "Jan", "Feb", "Mar" };

 

大致翻譯爲:c初學者通常會對二維數組和指針數組感到困惑,譬如給定如下定義 int a[10][20];  int *b[10];

那麼a[3][4]和b[3][4]都是有效的語法,都指向單個int數值。但是a是一個真正的二維數組,並且分配了200個int單元的內存空間,通常用傳統的矩形下標計算方式20*row+col來查找元素a[row,col] 。 然而b的定義僅僅分配了10個指針,並且並未初始化它們;必須要明確初始化。 假設每個b的元素確實都指向一個含20個元素的數組,那麼加上10個這樣的指針單元,就共分配了200個int單元。

指針數組的最大優勢是數組每一行的長度可以不同。因此,b的每個元素並不需一定指向含20個元素的矢量,它也可以指向兩個元素,50個元素,或值甚至爲空。

儘管我們用int來討論了這個問題,至今指針數組卻最常用在存儲不同長度的字符串,比如month_name函數。

(翻譯若有誤,請多指教,謝謝!)

其它不多說,上完整源代碼,更能清晰地表達兩者用法的不同:

#include <stdio.h>

//二維數組首地址弱化爲指針後,函數體內讀取數組值時,要通過row*colums+column的方式讀取
bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;

    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns-1;
        while (row < rows && column >= 0)
        {
            if(matrix[row*columns + column] == number)
            {
                found = true;
                break;
            }
            else if(matrix[row*columns + column] > number){
                --column;
            }
            else {
                ++row;
            }
        }
    }
    return found;
}

//若要函數體內能直觀p[i][j]取值,參數爲int**,傳的是二級指針,並非二維數組,見Test3
bool FindTwoDimention(int** matrix, int rows, int columns, int number)
{
    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns-1;
        while (row < rows && column >= 0) {
            if(matrix[row][column] == number)
                return true;
            else if (matrix[row][column] > number) {
                --column;
            }
            else {
                ++row;
            }
        }
    }
    return false;
}

void Test(const char* testname, int* matrix, int rows, int columns, int number)
{
    bool b = Find(matrix,rows,columns,number);
    if(b)
    {
        printf("%s passed.\n",testname);
    }
    else {
        printf("%s failed.\n", testname);
    }
}

void TestTwoDimention(const char* testname, int** matrix, int rows, int columns, int number)
{
    bool b = FindTwoDimention(matrix,rows,columns,number);
    if(b)
    {
        printf("%s passed.\n",testname);
    }
    else {
        printf("%s failed.\n", testname);
    }
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 查找的數字介於數組中的最大值和最小值之間
void Test1()
{
 //   int matrix[][4] = {{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
    int matrix[] = {1,2,8,9,2,4,9,12,4,7,10,13,6,8,11,15};
    Test("Test1",matrix,4,4,7);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 查找的值在最大值和最小值之間,但數組中沒有
void Test2()
{
    int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    int *p = &matrix[0][0];
    Test("Test2", p, 4, 4, 5);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 查找值爲數組最小值,於左上角. 將二維數組賦值給二級指針int**
void Test3()
{
    int p[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    //二維數組實際只是一個指向int的指針,而int**是二級指針,完全不等價。不能強制轉換
 //   int **matrix;
 //   matrix = new int*[4]; //row, 或者如下寫法
    int* matrix[4]; //row,指向指針int*的數組,分配的內存不連續,
                    //下面TestTwoDimention中做參數時自動弱化成int**指針
    for (int i=0; i<4; i++) {
        matrix[i] = new int[4]; //column
    }
    for (int i=0; i<4; i++) {
        for (int j=0; j<4; j++) {
            matrix[i][j] = p[i][j];
        }
    }

    TestTwoDimention("Test3", matrix, 4, 4, 1);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 查找值爲數組最大值,於右下角
void Test4()
{
    int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    Test("Test4", (int*)matrix, 4, 4, 15);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
//查找值小於最小值,不存在
void Test5()
{
    int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    Test("Test5", (int*)matrix, 4, 4, 0);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 查找值大於最大值,不存在
void Test6()
{
    int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}};
    Test("Test6", (int*)matrix, 4, 4, 16);
}

//傳空指針
void Test7()
{
    Test("Test7", nullptr, 0, 0, 16);
}

int main()
{
    Test1();
    Test2();
    Test3();
    Test4();
    Test5();
    Test6();
    Test7();

    return 0;
}

測試結果如下:

Test1 passed.
Test2 failed.
Test3 passed.
Test4 passed.
Test5 failed.
Test6 failed.
Test7 failed.
Press <RETURN> to close this window...

如上, Test1直接以一維數組傳入二維數組的值,做參數時自動弱化成int*。Test2定義的二維數組,但是手動將二維數組的首地址賦給了int*。Test4,5,6,7都是傳參時強制將二維數組首地址轉換成int*,c++新標準是會報警的,正確方式當Test2。 Test3定義了二級指針作爲參數,只能在調用前將二維數組的值一一賦值給int**。

參考文章:https://www.cnblogs.com/weiweisuhe/p/5691737.html

https://blog.csdn.net/nice__xixi/article/details/82081595

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章