【C语言篇】☞ 10. 数组、常见算法、模拟栈操作

数组

1. 概念:

  一组具有相同数据类型的数据的有序集合。

数组名是一个地址(是常量),不可改变、不能赋值、不能做左值。

int a[3]; // 定义了一个名称叫做a的数组, 数组中可以存放3int类型的数据

2. 初始化

  1int a[5]={1,2,3,4,5};     //常用

  2int a[5]={1,2,3};         //部分初始化,剩余的元素初始化为0,即{12300}

  3int a[5]={[0]=1,[1]=2,[3]=4,5}; //指定初始化,即{12045}

  4int a[5]={0};             //常用,初始化清零 {00000}

  5int a[5]={};              //不推荐(可读性不好) {00000}

  6int a[]={1,2,3,4,5};      //不推荐 省略数组的长度,可读性不好

3. 数组长度

  数组的长度 = size(数组) / size(元素)

    // 动态计算数组的元素个数

    int a[5];

    int length = sizeof(a) / sizeof(a[0]);

  数组名a是空间的首地址: a == &a[0] == &a

  

  

  • 问题一:为什么在函数形式参数的声明中*a与a[]是一样的?

  上述两种形式都说明我们期望的实际参数是指针。在这两种情况下,对a可进行的运算是相同的。而且,可在函数体内给a本身赋予新的值。C语言要求数组名只能用作“常量指针”,但对于数组型形式参数的名字没有这一限制。

  • 问题二:请问i[a]与a[i]是一样的吗?

  是的,它们是一样的。对于编译器而言,i[a]等同于*(i+a),而*(i+a)也就是*(a+i),我们知道,*(a+i)等效于a[i]。虽然,i[a]与a[i]是等效的,但一般我们在编程中只使用a[i],很少使用到i[a]。   

  • 问题三:数组下标越界的后果是什么?

  C语言中数组下标越界,编译器是不会检查出错误的,但是实际上后果可能会很严重,比如程序崩溃、程序的其他数据被改变等,所以在日常的编程中,程序员应当养成良好的编程习惯,避免这样的错误发生。

/**

 *  数组的拷贝

 */

#include <stdio.h>

int main() {

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

    int b[5];

    for (int i = 0; i < 5; i++) {

        b[i] = a[i];

        printf("%d ", b[i]);

    }

    printf("\n");

    return 0;

}

二维数组

1. 概念

  二维数组其实是数组的数组,可以将二维数组理解为一张几行几列的二维表。

二维数组: 数组中的每一个元素又是一个数组, 那么这个数组就称之为二维数组

元素类型 数组名称[一维数组的个数][每个一维数组的元素个数];

元素类型 数组名称[行数][列数];

 

//明确的告诉二维数组: 2个一维数组,每个一维数组有3个元素

// {{1, 2, 3}, {4, 5, 6}};

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

数组的名称就是数组的地址(首地址): &a == a == &a[0]

2. 初始化

int a[3][2]={{1,2},{3,4},{5,6}}; //对应元素内容

int b[3][2]={1,2,3,4,5,6}; //整体赋值

int c[3][2]={1,2,3,4}; //依次赋值,自动补零

 

注意点: 每个一维数组的元素个数不能省略

char name[][2] = {'r', 'b', 'h', 'j'};//OK

// 搞不清楚应该分配多大的存储空间, 以及搞不清楚应该把哪些数据赋值给第一个数组, 以及哪些数据赋值给第二个数组

char name1[2][] = {'r', 'b', 'h', 'j'};//ERROR

char name2[2][2] = {'r', 'b', 'h', 'j'};//OK

 

   return;结束当前函数!

exit(0);退出整个程序。(包含<stdlib.h>头文件)

  注:数组作为函数的参数传递(是地址传递), 在函数内部修改形参的值会影响到函数外部的实参值。

常见的一些算法

  选择排序、冒泡排序、折半查询(二分查找)、查表法(转进制)

三种排序算法可以总结为如下:

  • 都将数组分为已排序部分和未排序部分。
  • 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。
  • 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。
  • 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

1. 选择排序

  第1趟:在n个数中找到最小(大)数与第一个数交换位置

  第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置

      重复这样的操作...依次与第三个、第四个...数交换位置

  第n-1趟,最终可实现数据的升序(降序)排列。

  选择排序的特点:最值出现在起始端

   

2. 冒泡排序

  第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置(两两比较n-1次)

  第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置(两两比较n-2次)

      ……      ……

  第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置(两两比较1次)

  冒泡排序的特点:相邻元素两两比较,比较完一趟,最值出现在末尾

  

3. 折半查找

  优化查找时间(不用遍历全部数据)

折半查找的原理:

    1> 数组必须是有序的

    2> 必须已知minmax(知道范围)

    3> 动态计算mid的值,取出mid对应的值进行比较

    4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1

    5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1

/**

 *  已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置

 */

#include <stdio.h>

#include <time.h>

//1:遍历数组逐个查找

int findKey(int *arr, int length, int key) {

    for (int i = 0; i < length; i++) {

        if (arr[i] == key) {

            return i;

        }

    }

    return -1;

}

//2:折半查找

int findKey1(int *arr, int length,int key) {

    int min = 0, max = length - 1, mid;

    while (min <= max) {

        mid = (min + max) / 2; //计算中间值

        if (key > arr[mid]) {

            min = mid + 1;

        } else if (key < arr[mid]) {

            max = mid - 1;

        } else {

            return mid;

        }

    }

    return -1;

}

int main() {

    int arr[10000] = {0, [9995] = 1, 3, 6, 10, 12};

    int key = 3;

    int length = sizeof(arr) / sizeof(arr[0]);

    clock_t startTime = clock();

//    int index = findKey(arr, length, key);//共消耗了27毫秒!

    int index = findKey1(arr, length, key); //共消耗了1毫秒!

    clock_t endTime = clock();

    printf("共消耗了%lu毫秒!\n", endTime - startTime);

    printf("key对应的下标位置: %d\n", index); // 9996

    return 0;

}

  

/**

 *  已知一个有序的数组, 要求给定一个数字, 将该数字插入到数组中, 使数组还是有序的

 *  分析:其实就是找到需要插入数字的位置(min的位置)

 */

#include <stdio.h>

 

int insertValue(int *arr, int length,int key) {

    int min = 0, max = length - 1, mid;

    while (min <= max) {

        mid = (min + max) / 2; //计算中间值

        if (key > arr[mid]) {

            min = mid + 1;

        }

        if (key < arr[mid]) {

            max = mid - 1;

        }

    }

    return min;

}

int main() {

    int a[5] = {1, 3, 5, 7, 9};

    int key = 4;

    int length = sizeof(a) / sizeof(a[0]);

    int insertIndex = insertValue(a, length, key);

    printf("需要插入的位置是: %i\n", insertIndex);

    return 0;

}

4. 进制查表法

  可以打印输出任意进制

  

  

  

封装优化 进制查表法:

/**

 * 查表法(封装优化)

 */

#include <stdio.h>

/**

 *  转换所有的进制

 *  value:  就是需要转换的数值

 *  base:   就是需要&上的数

 *  offset: 就是需要右移的位数

 */

void total(int value, int base, int offset)

{

    // 1.定义一个数组, 用于保存十六进制中所有的取值

    char charValue[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    // 2.定义一个数组, 用于保存查询后的结果

    char result[32] = {'0'};

    // 3.定义一个变量, 用于记录当前需要存储到查询结果数组的索引

    int index = sizeof(result) / sizeof(result[0]);

    // 4.循环取值

    while (value != 0) {

        // 1) 取出1位的值

        int res = value & base;// base: 1 7 15

        // 2) 利用取出来得值到表中查询对应的结果

        char ch = charValue[res];

        // 3) 存储查询的结果

        result[--index] = ch;

        // 4) 移除二进制被取过的1

        value = value >> offset;// offset: 1 3 4

    }

    // 5.打印结果

    for (int i = index; i < 32; i++) {

        printf("%c", result[i]);

    }

    printf("\n"); 

}

/** 转二进制 */

void printBinary(int num) {

    total(num, 1, 1);

}

/** 转八进制 */

void printOct(int num) {

    total(num, 7, 3);

}

/** 转十六进制 */

void printHex(int num) {

    total(num, 15, 4);

}

int main() {

    printBinary(10); //1010

    printOct(23);    //27

    printHex(1004);  //3ec

    return 0;

}

 

  

   

模拟栈的操作

  

  

 

 

 

posted @ 2017-02-15 16:11  专注·精彩  阅读(789)  评论(0编辑  收藏  举报