—— 归纳总结 ——
◉ 序 & 纲要
以下的所有算法全部以函数的形式出现,可以直接单独拿出来用
◉ 排序 & 查找
—① 冒泡排序
void sort1(int array[], int len)
{
int temp; // 交换两数
for (int i = len - 1; i > 0; i--)
{
for (int j = 0; j < i; j++)
{
if (array[j] > array[j + 1])
{
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
解释:
- i 的初始值:len - 1 ,表示 i 从数组的最后一项开始,往前缩。
- i 的临界值:0 ,表示直到缩至数组的第二项排序完毕
- j 的初始值:0 ,表示每次都从第一项开始与下一项进行比较
- j 的临界值:i ,表示走到数组的倒数第二项停下,因为有 j+1 存在,不能走到最后一项停,倒数第二项就覆盖整个数组了
—② 选择排序
void sort2(int array[], int len)
{
int temp; // 交换两数
int min; // 记录当前最小值
for (int i = 0; i < len - 1; i++)
{
min = i; // 把最小值置为当前值
for (int j = i + 1; j < len; j++)
{
if (array[j] < array[min]) min = j; // 保证 min 是当前最小
}
// 如果找到新的最小值,则交换
if (min != i)
{
temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
}
解释:
- i 的初始值:0 ,表示 从第一项开始,找出后面的最小值与其交换
- i 的临界值:len - 1 ,表示这种交换持续到倒数第二个数
- j 的初始值:i + 1 ,表示每次都从 i 的下一项开始遍历,找到比 i 小的值中最小的值(由 min 确定该最小值)
- j 的临界值:len ,表示每次都遍历到数组的最后一个数
—③ 快速排序
void sort3(int array[], int left, int right)
{
if (right - left <= 1) return; // 剩下的少于一个数的话就不用再排序了
int i = left, j = right; // 设置左下标和右下标的位置
int pivot = array[i]; // 把第一个设置为基准
while (i < j)
{
while (i < j && array[j] >= pivot) j--; // 向左检索,直到找到比基准小的值
array[i] = array[j];
while (i < j && array[i] <= pivot) i++; // 向右检索,直至找到比基准大的值
array[j] = array[i];
}
array[i] = pivot; // 将基准放到中间位置,此时 i==j ,使用哪个都可以
// 递归再次排序,直到 len 为 1,注意除去中间的基准
sort3(array, left, i - 1); // 左边的继续排
sort3(array, i + 1, right); // 右边的继续排
}
解释:
- 定义一个基准 pivot ,选为第一个元素
- 然后定义两个下标分别从前往后和从后往前遍历
- 后面的下标先动起来向前检索,(以升序为例)找到比基准小的值后,将该值赋给前下标所指的元素
- 然后前下标向后检索,找到比基准大的值后,将该值赋给后下标所指的元素
- 重复以上两步,直到两个下标相遇,将基准的值赋给该下标所指的元素
- 现在得到的就是被基准分割的左右两堆数,左边的数都比基准小,右边的数都比基准大
- 然后进行 递归 ,将左右两边的两堆数分别再进行快速排序,不断递归,直到数堆被分得只剩下一个,表示排序完毕
- 得到的数组中,每一个数的左边都是比它小的,右边都是比它大的,这就实现了升序排序
—④ 插入排序 (2021-4-17)
// 插入排序算法
void insertSort(int* a, int n){
// 分别负责:遍历数组,寻找插入下标,进行插入,记录要插入的数
int i, j, k, temp;
// 遍历整个数组
for (i = 1; i < n; i++) {
// 找到比该数小的数的下标
for (j = i - 1; j >= 0 && a[j] > a[i]; j--);
// 记录要插入的数
temp = a[i];
// 将 j 之后的元素往后挪一位
for (k = i; k - 1 > j; k--) a[k] = a[k - 1];
// 将该数插入到找到下标的后面
a[k] = temp;
}
}
解释:
- 从第二个元素开始作为插入元素,比较与前面全部元素的大小,找到比该插入元素小的数组下标,然后插入到它的后面
- 分别用三个变量循环,
i用于遍历整个数组,j用于找到比插入元素小的数组下标,k用于将从该下标开始到要插入的元素全部向后挪动 1 位(注意此时要插入的元素会被覆盖,应提前存放) - 最后将要插入的元素插入到找到下标的后面,循环完成排序
—⑤ 归并排序 (2021-4-17)
// 归并排序(合并数组)
void MergeArray(int* a, int begin, int mid, int end, int* temp){
// i 遍历前一个数组,j 遍历后一个数组,k 给 temp 数组赋值
int i = begin, j = mid + 1, k = begin;
while (i <= mid && j <= end) {
// 每次将数组中头部较小的放进 temp
if (a[i] < a[j]) {
temp[k] = a[i];
i++;
}
else {
temp[k] = a[j];
j++;
}
k++;
}
// 将剩下的元素依次放进 temp 数组
while (i <= mid) {
temp[k] = a[i];
i++;
k++;
}
while (j <= end) {
temp[k] = a[j];
j++;
k++;
}
// 将 temp 存储的数据放到 a 中
for (int i = 0; i < k; i++) a[i] = temp[i];
}
// 归并排序
void MergeSort(int* a, int begin, int end, int* temp){
// 不重合时继续拆分
if (begin != end) {
MergeSort(a, begin, (begin + end) / 2, temp);
MergeSort(a, (begin + end) / 2 + 1, end, temp);
MergeArray(a, begin, (begin + end) / 2, end, temp);
}
}
解释:
- 用到 a 表示元素组,temp 表示中间数组,用于临时存放要排序的元素(最后 a 和 temp 两个数组是相同的)
- 将数组不断对半分直到剩下一个元素(此时只有 1 个元素,视为已排序好)
- 然后将相邻两个合并,再将合并后的数组与另一个数组进行合并,合并到最后就是排序好的数组了
上图容易理解:
—⑥ 二分查找法
// 算法4 :二分查找法
int binarySearch(int array[], int len, int data)
{
int low = 0, high = len - 1;
int mid = (low + high) / 2;
while (low <= high)
{
if (data < array[mid]) high = mid - 1;
else if (data > array[mid]) low = mid + 1;
else return mid;
mid = (low + high) / 2;
}
return -1;
}
解释:
- 该查找方法生效的 前提是数组已排序完毕 ,升序降序都可以
- 首先将 low 下标和 high 下标指向数组的两头,由此得到 mid 下标
- 将要查找的数与 mid 对应的数比较,(以升序为例)比 mid 指向的数大则往右查找,小则往左查找
- 直到 low 大于 high 则表示查找完毕
注意点:
每次缩小范围时,往右缩需要 mid+1,往左缩需要 mid-1,后面的 +1 或 -1 如果漏了将导致死循环。之所以这样做是因为 mid 已经查找过了在比较相等的时候,此时可以忽略它
◉ 链表
—① 插入结点(四种方法)
共有四种插入方法:头插法 、尾插法 、随机插法 和 下标插法 (仅个人划分习惯)
新建一个结点
struct link* newNode(int data)
{
struct link* node = (struct link*)malloc(sizeof(struct link));
node->data = data;
node->next = NULL;
return node;
}
—— 1、头插法
void insertHead(struct link **head, int data)
{
struct link* node = newNode(data); // 新建一个结点
if (*head == NULL) *head = node; // 如果链表为空,则将新节点置为头结点
else
{
node->next = *head;
*head = node; // 顺序不能倒
}
}
头插法直接改变头结点,应该直接对原头结点操作
—— 2、尾插法
void insertEnd(struct link** head, int data)
{
struct link* node = newNode(data);
struct link* xhead = *head; // 替换操作对象,不影响到头结点
if (xhead == NULL) *head = node; // 头结点为空,直接替换成新节点
else
{
while (xhead->next != NULL) xhead = xhead->next; // 遍历到尾结点
xhead->next = node;
}
}
—— 3、随机插法 (自动升序)
void insertSort(struct link** head, int data)
{
struct link* node = newNode(data); // 新建一个结点
struct link* xhead = *head; // 替换操作对象,不影响到头结点
struct link* before = NULL; // 定义前一个结点,用于中间插入
if (xhead == NULL) *head = node; // 头结点为空,直接替换成新节点
else
{
while (xhead != NULL && data > xhead->data) // 从头遍历,寻找到比插入结点数据大的结点
{
before = xhead;
xhead = xhead->next;
}
if (before == NULL) // 如果前结点为空,则说明该数据比第一个数据还小,需要进行头插法
{
node->next = *head;
*head = node;
}
else // 否则进行中间插法(即使是尾部插入也是行得通的~)
{
node->next = xhead;
before->next = node;
}
}
}
—— 4、下标插法 (指定下标插入)
void insertIndex(struct link** head, int data, int index)
{
struct link* node = newNode(data); // 新建一个结点
struct link* xhead = *head, *temp; // 替换操作对象,不影响到头结点, 中间变量
if (xhead == NULL) *head = node; // 头结点为空,直接替换成新节点
else
{
for (int i = 0; i < index - 1; i++) // 遍历到下标结点的头一个( 注意 0 和 1 都会直接跳过)
{
if (xhead->next == NULL) break;
xhead = xhead->next;
}
if (index == 0) // 0 表示从头部插入,应更换头结点
{
node->next = *head;
*head = node;
}
else
{
node->next = xhead->next;
xhead->next = node;
}
}
}
注意点:
- 插入结点时新建的结点 必须通过 malloc 开辟空间 ,不能直接使用局部变量,因为当该函数结束时局部变量就没了,结点将添加失败
- 尾插法 / 随机插法 添加节点时要注意 原头结点的位置 ,如果直接遍历头结点的话会导致添加完毕后头结点指到最后面去了,应该将它还原到最前面。
- 这里使用的是 临时操作指针 方法,不会影响到原头结点
—② 删除结点
// 删除结点
void deleteNode(struct link** head, int data)
{
struct link* del = *head, * before = NULL;
while (del != NULL)
{
if (del->data == data)
{
if (before == NULL) *head = (*head)->next;
else
{
before->next = del->next;
before = del;
}
// 这里的 break 直接影响到是删除全部的 data 结点还是只删第一个
// break 去掉将是删除全部 data结点
break;
}
else before = del;
del = del->next;
}
}
要删除全部 data 结点的话则将 break 去掉,加上 break 表示删除第一个 data 结点
—③ 查找中间结点
link* FindMidNode(struct link* L) {
// 如果没有初始化或者只有头指针,返回空
if (L == NULL || L->next == NULL) return NULL;
link* slow, * fast;
slow = L, fast = L;
while (fast != NULL)
{
fast = fast->next;
if (fast == NULL) break;
else fast = fast->next;
slow = slow->next;
}
return slow;
}
—④ 判断链表是否成环
int IsLoopList(link* L)
{
// 如果没有初始化或者只有头指针,返回空
if (L == NULL || L->next == NULL) return 0;
link* slow, * fast;
slow = L, fast = L;
while (fast != NULL)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return 1;
}
return 0;
}
—⑤ 偶数结点反转
void ReverseEvenList(link** L)
{
if (*L == NULL) return NULL;
link* head = *L, * temp = head->next;
// 两个位置都判断,才能兼容奇偶个数
while (temp != NULL && temp->next != NULL)
{
head->next = temp->next;
temp->next = temp->next->next;
head->next->next = temp;
head = head->next->next;
temp = temp->next;
}
}
◉ 数学算法
—① 判断素数
// 判断素数,是则返回1,不是则返回2
int isPrime(int num)
{
if (num <= 1) return 0;
for (int i = 2; i <= sqrt(num); i++) if (num % i == 0) return 0;
return 1;
}
—② 两数交换(三种方法)
—— 1、中间量法
void exchange1(int *num1, int *num2)
{
int temp;
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
注意不能写成:
temp = num1;
num1 = num2;
num2 = temp;
这样就又变为了值传递,只改变了局部指针变量的值
—— 2、加减乘除法
// 加减法
void exchange2(int* num1, int* num2)
{
*num1 = *num1 + *num2;
*num2 = *num1 - *num2;
*num1 = *num1 - *num2;
}
// 乘除法
void exchange3(int* num1, int* num2)
{
*num1 = *num1 * *num2;
*num2 = *num1 / *num2;
*num1 = *num1 / *num2;
}
这两种方法的本质差不多所以归为一种
都是先将两个数合并为一个数,然后再分别拆分达到交换的效果
—— 3、异或法
void exchange4(int* num1, int* num2)
{
*num1 = *num1 ^ *num2;
*num2 = *num1 ^ *num2;
*num1 = *num1 ^ *num2;
}
—② 最大公约数
int getGCD(int num1, int num2)
{
// 保证 num1 大于 num2
if (num1 < num2) exchange4(&num1, &num2);
int c = num1 % num2;
while (c != 0)
{
num1 = num2;
num2 = c;
c = num1 % num2;
}
return num2;
}
—③ 最小公倍数
int getLCM(int num1, int num2)
{
return num1 * num2 / getGCD(num1, num2);
}
—④ 其他进制转十进制
int changeToTen(int num, int system)
{
int i = 0;
int result = 0;
do
{
result += (num % 10) * pow(system, i);
i++;
} while ((num /= 10) != 0);
return result;
}
—⑤ 十进制转其他进制
// 十进制转其他进制
int changeToOther(int num, int system)
{
int i = 0;
int result = 0;
while (num != 0)
{
result = (num % system) * pow(10, i) + result;
i++;
num /= system;
}
return result;
}
以上两个只支持 10 以内的进制
未完待续(寒冰小澈)