数组和算法基础
目錄
一維數組的定義和初始化
數組的創建
?數組的初始化
數組的輸出
?數組的存放
數組名的調用
?數組的下標越界問題
二維數組的定義和初始化
數組的創建
數組的初始化
數組的輸出?
?數組的存儲
向函數傳遞一維數組
數組名的含義
數組作為參數傳遞給函數
排序和查找
冒泡排序
交換法排序
選擇法排序?
特定子項的排序
查找
線性查找
折半查找?
向函數傳遞二維數組
一維數組的定義和初始化
數組:一組數,是一組相同類型元素的集合
創建方式
type_t arr_name ?[const_n];
元素類型 ? ? ? ? 常量表達式,用來指定數組的大小
數組的創建
int main() {int arr[6];//創建整型數組char ch[9];//創建字符數組/*int n = 3;int arr1[n];*///錯誤的,不能將變量名作為下標使用return 0; }?數組的初始化
int main() {int arr1[5] = { 1,2,3,4,5 };//完全初始化int arr2[5] = { 1,2,3 };//不完全初始化,自動補0int arr3[]={1, 2, 3, 4, 5};//根據初始化內容確定大小,等價為int arr3[5] = { 1,2,3,4,5 };return 0; } int main() {char ch1[5] = { 'a','b','c' };//不完全初始化,自動補'\0'char ch2[] = { 'a','b','c' };//根據初始化內容確定大小char ch3[5] = "abc"; //前四個元素為 a b c \0 (\0)char ch4[] = "abc";//僅四個元素 a b c \0return 0; }?當數組在所有函數外定義,或用static定義為靜態儲存類型時,即使不給數組元素賦初值,那么數組元素也會自動初始化為0,這是在編譯階段完成的
int main() {char a[] = "abc";//四個元素char b[] = { 'a','b','c'};//三個元素printf("%s\n", a);printf("%s\n", b);//沒有結束標志,燙燙燙printf("%d\n", strlen(a));//3printf("%d\n", strlen(b));//隨機數return 0; }數組的輸出
int main() {int arr[10] = { 0 };arr[4] = 5;//下標引用操作符int i = 0;int sz = sizeof(arr)/ sizeof(arr[0]);//判斷數組元素個數for (i = 0; i < sz; i++){printf("%d", arr[i]);}return 0; } 運行結果: 0000500000?注意:數組的下標是從0開始的
?數組的存放
int main() {int arr[10] = { 0 };int i = 0;for (i < 0; i < 10; i++){printf("%p\n", &arr[i]);//%p是按地址的格式打印-進行補齊十六進制的打印}printf("%x\n", 0x12);printf("%p\n", 0x12);return 0; } 運行結果: 0000002D780FF668 0000002D780FF66C 0000002D780FF670 0000002D780FF674 0000002D780FF678 0000002D780FF67C 0000002D780FF680 0000002D780FF684 0000002D780FF688 0000002D780FF68C 12 0000000000000012根據數組中一個元素占據四個字節,所以我們可以發現,一維數組在內存中是連續存放的
隨著數組下標的增長,地址是由低到高變化的?
數組名的調用
int main() {int arr[5] = { 1,2,3,4,5 };int* p = arr;//數組名是數組首元素的地址int i = 0;for (i = 0; i < 5; i++){printf("%d", *p);p++;}return 0; } 運行結果: 12345?數組的下標越界問題
int main() {int a=1,c=2,b[5]={0},i;printf("%p %p %p\n", b, &c, &a);//用%p格式打印數組b,變量c和a的首地址for (i = 0; i <= 8; i++){b[i] = i;printf("%d ", b[i]);}printf("\nc=%d,a=%d,i=%d\n", c, a, i);return 0; }因不同的編譯系統為變量分配的內存地址有所不同,因此在不同的系統下運行程序可能會輸出不同的值
程序運行起來回報錯,輸出結果可能為2 1 9,也可能為5 6 9,出現第二種結果的原因是0-4對數組b進行賦值以后,5-8是多余的,造成了下標越界,多余的數則會在之后的訪問中出現?,即訪問c,a時,內存中變量a和c的值被修改為了6和5
b[0] b[1]?b[2]?b[3]?b[4] c a i?b[8]
?0? ? ?0? ? ?0? ? ?0? ? ?0? ? 2 1? ? ? ??
進行賦值后
b[0] b[1]?b[2]?b[3]?b[4] c a i?b[8]
?0? ? ?1? ? ?2? ? ?3? ? ?4? ? 5 6 9? 8
所以,我們應確保元素的正確引用,以免因下標越界而造成對其他存儲單元中數據的破壞?
二維數組的定義和初始化
數組的創建
int main() {int arr[3][4];//整型數組三行四列char ch[3][5];int arr1[][3];//二維數組的行可以省略,列不可以省略return 0; }但是在創建但不初始化的時候我們是不可以將二維數組的行省略的,因為這樣會導致數組的大小不確定?
n維數組用n個下標來確定各元素在數組中的順序
數組的初始化
int main() {//初始化-創建的同時賦值int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };int arr2[3][4] = { 1,2,3,4,5,6,7 };//整型數組自動補0,字符數組自動補\0int arr3[][4] = {1,2,3,4,5,6,7,8,9};//按照初始化列表中提供的初值個數來定義數組的大小//{1,2,3,4,5,6,7,8,9,0,0,0};//{{1,2,3,4},{5,6,7,8},{9,0,0,0}};這兩句和上面那一句是等價的int arr4[3][4] = { {1,2},{3,4},{5,6} };//分別對每一行初始化,每一行不足的自動補0return 0; }?對于二維數組而言,既可以按元組初始化,也可以按行初始化
arr[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}//按元素初始化
arr[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}//按行初始化
?上面這兩句本質上是等價的
int main() {int i = 0;int j = 0;int arr[][4] = { {1,2},{3,4},{5,6} };int* p = &arr[0][0];for (i = 0; i < 12; i++){printf("%d ", *p);p++;}return 0; } 運行結果: 1 2 0 0 3 4 0 0 5 6 0 0?注意:假設一個二維數組arr[3][4],第一行為arr[0][j],以此類推,所以,我們可以認為arr[i]是一個一維數組的數組名,arr[i][j]是這個一維數組中的某個元素
數組的輸出?
int main() {int i = 0;int j = 0;int arr[3][4] = { {1,2},{3,4},{5,6} };for (i = 0; i < 3; i++){for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0; }C語言中不帶下標的數組名具有特殊含義,它代表數組的首地址,因此不能整體引用一個數組,每次只能引用特定下標值的數組元素?
?數組的存儲
int main() {int i = 0;int j = 0;int arr[3][4] = { {1,2},{3,4},{5,6} };for (i = 0; i < 3; i++){for (j = 0; j < 4; j++){printf("arr[%d][%d]=%p ",i,j, &arr[i][j]);}printf("\n");}return 0; } 運行結果: arr[0][0]=000000B4FCEFFCB8 arr[0][1]=000000B4FCEFFCBC arr[0][2]=000000B4FCEFFCC0 arr[0][3]=000000B4FCEFFCC4 arr[1][0]=000000B4FCEFFCC8 arr[1][1]=000000B4FCEFFCCC arr[1][2]=000000B4FCEFFCD0 arr[1][3]=000000B4FCEFFCD4 arr[2][0]=000000B4FCEFFCD8 arr[2][1]=000000B4FCEFFCDC arr[2][2]=000000B4FCEFFCE0 arr[2][3]=000000B4FCEFFCE4因此,我們可以看出來,二維數組在內存中也是連續存放,每一行內部是連續的,行與行之間也是連續的 ,存完第一行接著存第二行,以此類推,所以數組的第二維地長度聲明是不可以省略的,系統必須知道每一行有多少個元素才能正確的計算出該元素相對于二維數組地第一個元素的偏移量
向函數傳遞一維數組
數組名的含義
數組名是什么?數組名是數組首元素的地址
int main() {int arr[10] = { 0 };int sz = sizeof(arr);printf("%d\n", sz);printf("%p\n", &arr[0]);printf("%p\n", arr);printf("%p\n", &arr);return 0; } 運行結果: 40//結果為什么是40?如果數組名是首元素地址的話不應該是4嗎? 000000746E5EFA88 000000746E5EFA88 000000746E5EFA88//為什么三者結果是一樣的?對于問題1,我們需要知道雖然數組名在調用時,是將數組地首地址傳了過去,但是我們需要知道有兩個特例:
(1)?sizeof(arr)-數組名表示整個數組,計算的是整個數組的大小,單位是字節
(2)&數組名-數組名表示整個數組,取出的是整個數組的地址
對于問題2,我們來看下面這一段代碼
int main() {int arr[10] = { 0 };printf("%p\n", &arr);printf("%p\n", &arr+1);printf("%p\n", arr);printf("%p\n", arr+1);return 0; } 運行結果: 0000002B06B4F958 0000002B06B4F980//二者相差0x28=40 0000002B06B4F958 0000002B06B4F95C//二者相差4所以二者雖然看上去一樣,但是含義不同
數組作為參數傳遞給函數
若要把一個數組傳遞給一個函數,那么只要使用不帶方括號的數組名作為函數實參調用函數即可
數組名代表數組第一個元素的地址,因此用數組名作函數實參實際上是將數組的首地址傳給被調函數
一維數組作為函數形參時,數組的長度不可以出現在數組名后面的方括號內,通常用另一個整型形參指定數組的長度,當然我們也可以在方括號中寫入
用數組名作為函數實參時,形參數組和實參數組既可以同名也可以不同名,因為它們的名字代表的是數組的首地址,它們都指向了內存中的同一段連續的存儲單元;而用簡單變量作為函數實參時,由實參向形參單向傳遞的是變量的內容,不是變量的地址,因此無論它們是否同名,它們都代表內存中不同的存儲單元
在被調函數中改變形參數組元素值時,實參數組元素值也會隨之改變,這種改變并不是形參反向傳給實參造成的,而是因為形參和實參具有同一地址,共享同一段內存單元造型成的
排序和查找
冒泡排序
交換法排序
將第一個元素與后面的元素進行比較,一旦比這個元素要小,那么二者就交換順序,將交換順序之后的第一個元素再與上回比較的位置繼續向后比較,進行n-1次比較之后,再將第二個元素依次進行比較n-2次,以此類推
#include"Bubble_Sort.h" int main() {int arr[] = { 2,3,1,4,5,0 };int sz = sizeof(arr) / sizeof(arr[0]);//計算數組元素個數bubble_sort(arr,sz);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0; }?
void bubble_sort(int arr[], int sz)//形參arr本質是指針,arr[]=*arr {int i = 0;int j = 0;for (j = 0; j < sz - 1; j++)//初始化語句也可以改為j=i+1,判斷條件改成j<n{for (i = 0; i < sz - 1 - j; i++){int tmp = 0;if (arr[i] > (arr[i+1])){tmp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = tmp;}}} }此處將頭文件略去,但不代表不需要寫?
如果我們將數組名改為指針形式,代碼則是如下樣式
void Bubble_Sort(int* arr, int sz) {int i = 0;int j = 0;for(j=0;j<sz-1;j++,arr++){for (i = 0; i < sz - 1-j; i++){int tmp = 0;if (*arr > *(arr + 1 + i)){tmp = *arr;*arr = *(arr + 1 + i);*(arr + 1+i) = tmp;}}} }選擇法排序?
上面所使用的的交換法排序,第一個最多交換n-1次,第二個n-2次...,這樣排序起來效率太過于低下,所以我們也可以使用選擇法排序
先將第一個數和第二個數比較,再與第三個數比較,以此類推,每次比較之后都將較小的數的下標進行保存,這樣我們只需要在每一輪循環最后比較一下我們保存的元素的下標和需要排序的位置數的下標是否相同就可以了,不相同就進行交換,這樣每輪最多一次交換操作,一套下來最多n-1次交換操作
void bubble_sort(int* arr, int sz) {int i, j, k, temp;for (i = 0; i < sz - 1; i++){k = i;for (j = i + 1; j < sz; j++){if (arr[j] < arr[k]){k = j;}}if (k != i){temp = arr[k];arr[k] = arr[i];arr[i] = temp;}} }特定子項的排序
對信息進行排序時,通常只使用信息的一個子項作為鍵值,由鍵值決定信息的全部子項的排列順序
假如存在數組a,數組b,且兩個數組中的元素個數相等,數組a中的第i個元素對應數組b中第i個元素,當我們需要根據數組b中的元素大小進行排序時,我們可以同時將數組a和數組b作為參數傳遞給函數,對b中的元素進行冒泡排序,之后再進行交換的操作時加一個將數組a中的元素也進行交換的操作即可
temp1 = arr1[k];arr1[k] = arr1[i];?arr1[i] = temp;
temp2 = arr2[k];arr2[k] = arr2[i];?arr2[i] = temp;
查找
當用戶輸入相關的一個信息時,我們進行搜索這個元素的過程稱為查找,我們所要學習的有線性查找和折半查找
線性查找
使用循環查找值為x的數組元素,若找到則返回x在數組中的下表位置,沒找到則返回-1
int Search(int arr[], int x, int n) {int i;for (i = 0; i < n; i++){if (arr[i] == x){return i;}}return -1; }折半查找?
折半查找也稱二分查找,這種查找方式我們之前已經講過,在這里就不在做過多的說明
注意:
(1)雖然折半查找速度比線性查找要快很多,但是在使用折半查找時需要保證元素的大小順序是排好的,不可以是亂序,所以我們在使用折半查找之前,一般會對數組進行排序操作
(2)在進行折半查找時,我們一般會將計算中間值mid語句寫為:
mid=(left+right)/2
看似沒有什么問題,但是如果二者的加和過大,超出了limits.h中定義的有符號整數的極限值,那么將會導致mid變成負數,所以在這里我們不妨將mid的計算語句寫為:
mid=較小值+(較大值-較小值)/2
向函數傳遞二維數組
二維數組向函數傳遞和一維數組向函數傳遞實際上并沒有太大的區別,唯一要注意的一點是,二維數組作為形參時,第二維的長度不可以省略,不可以像一位數組一樣空下之后在后面使用其他整型形參指定數組長度,但是第一維長度可以
總結
- 上一篇: 2022年欧盟的寒冬,中国外贸企业送温暖
- 下一篇: 第一本Docker书pdf