16 算法

N皇后-回溯法

07e03076699f9e27a54b6867d7ed423

非递归

// 标准输入输出流
#include <math.h>
#include <stdio.h>

// 定义1个N x N的棋盘
// 这是一个宏定义,属于C语言的预处理指令
// 代码中所有出现 N 的地方都会被替换为 4
// 不可修改,调试的时候,看不到N(替换后符号消失)
// 定义1个标识符, 常量 4. 表示4个皇后
#define N 10

// 定义了一个名为 q 的整型数组
// 由于N=4,所以数组的大小为5
// 存储皇后的列号
int q[N + 1];

// 检查第j个皇后的位置是否合法
// 由于不同的皇后不会放在同一行,所以不用判断行
// 只需要判断两个皇后不在同一列并且不在一条斜线上, 即认为当前皇后的位置合法;反之不合法
// j表示当前正在摆放的第j个皇后
int check(int j) {
    // 如果当前皇后和它之前的每一个皇后相比,
    // 在同一行或者同一个斜线上,认为位置不合法,否则对比完它之前的所有皇后之后,位置都是合法的, 则认为位置合法
    for (int i = 1; i < j; ++i) {
        if (q[i] == q[j] || abs(i - j) == abs(q[i] - q[j])) {
            // 表示在同一列或者同一个斜线上.位置不合法
            return 0;
        }
    }
    return 1;
}

// 求解N皇后的方案
void queue() {
    // 从1开始,q[i]表示第i个皇后
    for (int i = 1; i <= N; ++i) {
        q[i] = 0; // 把每个皇后的列都定义为0,表示全都没有放入棋盘.反之>0,比如q[i] = 1,表示第1个皇后放在了第一行的第一列
    }

    // 方案数
    int answer = 0;

    // 表示正在摆放第j个皇后
    int j = 1;
    // i <=N 表示要摆放N个皇后
    while (j >= 1) {
        // 表示将第j个皇后,向右移动1个位置.
        // 初始的时候,表示将第1个皇后从棋盘外(q[0]移动到第一行的第1列)
        q[j]++;

        while (q[j] <= N && !check(j)) {
            // 这里必须要加上=N,如果不加.就会少一个越界的场景.导致由于越界后需要调整上一个皇后位置的场景丢失.最终导致数据错乱
            q[j] = q[j] + 1;
        }

        if (q[j] <= N) {
            if (j == N) { // 找到了N皇后的一组解
                answer++;
                printf("方案%d:  ", answer);
                for (int i = 1; i <= N; ++i) {
                    printf("%d,", q[i]);
                }
                printf("\n");
            } else {
                // 继续摆放下一个皇后的位置
                j++;
            }
        } else {
            // 越界,回溯
            q[j] = 0;
            j--;
        }
    }
}

int main(int argc, char const *argv[]) {
    queue();

    return 0;
}

递归

#include <math.h>
#include <stdio.h>

#define N 3

int answer = 0;

// 定义1个一维数组
// 假设有4个皇后, 每个皇后占1行,
// j从1开始, 下标j表示第j个皇后,q[j]的值表示皇后的位置列.所以1个皇后的位置可以表示为 j,q[j].
// 即第j个皇后在第j行的第q[j]列
int q[N + 1];

/**
 * 检查当前皇后的位置是否合法
 * @param j 从1开始,表示当前正在被摆放的皇后的位置列
 * @return 1 表示合法;0表示不合法
 */
int check(int j) {
    for (int i = 1; i < j; i++) {
        // 当前摆放的皇后的列和以往的每一个皇后在同一列,则认为不合法;
        // 或者
        // 当当前皇后的行号-每一个皇后的行号的绝对值等于两个皇后的列号的绝对值,同样认为不合法
        if (q[i] == q[j] || abs(i - j) == abs(q[i] - q[j])) {
            return 0;
        }
    }
    return 1;
}

/**
 * 求解N皇后问题
 */
void queue(int j) {
    q[j] = q[j] +1;
    for (int i = 1; i <= N; ++i) {
        q[j] = i;
        if (check(j)) {
            if (j == N ) {
                //找到一组N皇后解
                answer++;
                printf("方案%d:  ", answer);
                for (int i = 1; i <=N ; ++i) {
                    printf("%d, ", q[i]);
                }
                printf("\n");
            } else {
                queue(j + 1);
            }
        }
    }
}

int main() {
    for (int j = 1; j <= N; ++j) {
        q[j] = 0;
    }

    // 一开始将第一个皇后放入到第一行的第一列
    queue(1);
    return 0;
}

归并排序-分治法

写法1

#include <stdio.h>

void merge(int arr[], int begin, int mid, int end) {
    // 变量抽离出来,后续可能复用,并且看代码也清楚明了
    int left_len = mid - begin + 1;
    int right_len = end - mid;
    int left[left_len], right[right_len];

    // Copy data to temp arrays
    //把指定区间的数据拷贝到 左右两个数组中. 那么arr就可以置换出来了.也就不依赖arr了
    //换言之,arr的[begin,end]区间,可以直接覆盖值.少定义1个目标临时数组
    for (int i = 0; i < left_len; i++)
        left[i] = arr[begin + i];
    for (int j = 0; j < right_len; j++)
        right[j] = arr[mid + 1 + j];

    // Merge the temp arrays back into arr[begin..end]
    //定义目标数字从begin位置开始依次放入数据
    //这里选择新定义1个变量k,并且把begin赋值给k,避免造成begin污染
    //i<做集合的length,同理右边也是.为了防止数组越界
    //当这个循环结束后.说明至少有1边的数组已经全部放入到了目标数组.那么只需要将另外1个数组的剩余部分全部依次放入目标数组即可
    int i = 0, j = 0, k = begin;
    while (i < left_len && j < right_len) {
        if (left[i] <= right[j]) {
            arr[k++] = left[i++];
        } else {
            arr[k++] = right[j++];
        }
    }

    // Copy remaining elements of left[] if any
    while (i < left_len) {
        arr[k++] = left[i++];
    }

    // Copy remaining elements of right[] if any
    while (j < right_len) {
        arr[k++] = right[j++];
    }
}

void mergeSort(int arr[], int begin, int end) {
    if (begin < end) {
        int mid = begin + (end - begin) / 2;  // Avoid potential overflow
        mergeSort(arr, begin, mid);
        mergeSort(arr, mid + 1, end);
        merge(arr, begin, mid, end);
    }
}

int main() {
    int arr[] = {90, 1, 100, 5, 20};
    int n = sizeof(arr) / sizeof(arr[0]);

    mergeSort(arr, 0, n - 1);

    printf("Sorted array: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

<<<<<<< HEAD

0-1背包/最大公共子序列/矩阵连乘-动态规划

image-20250416213707819


image-20250416215038532


image-20250416224649338


=======

写法2 (推荐)

区别, 这种写法, 没有显式定义两个辅助数组

#include <stdio.h>

/**
 * 归并排序分为3部分
 * 1. 拆分
 * 2. 求解
 * 3. 合并
 * @return
 */
void merge(int arr[], int low, int mid, int high) {
    int len = sizeof(arr) / sizeof(arr[0]);
    int tmpArr[len];
    for (int i = low; i <= high; ++i) {
        tmpArr[i] = arr[i];
    }
    int i = low;  //表示左边数组移动的指针(下标)
    int j = mid +1; //表示右边数组移动的指针
    int k = low; //表示目标数组拜访位置的指针
    while (i <= mid && j <= high) {
        if(tmpArr[i] <= tmpArr[j]){
            arr[k++] = tmpArr[i++];
        } else{
            arr[k++] = tmpArr[j++];
        }
    }
    while (i <= mid) arr[k++] = tmpArr[i++];
    while (j <= high) arr[k++] = tmpArr[j++];
}

void mergeSort(int arr[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);
        merge(arr, low, mid, high);
    }
}

int main() {
    int arr[5] = {4, 3, 6, 2, 7};
    int n = sizeof(arr) / sizeof(arr[0]);
    mergeSort(arr,0, n-1);
    for (int i = 0; i < n; ++i) {
        printf("%d ,", arr[i]);
    }
    return 0;
}

b82f721409342e41c00d9744011c0390bba8d5d9