南邮数据结构作业 - 第二章

第一题:设计算法,将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变。

  • 算法思想
    要求是将单链表中序号奇数和偶数的元素分到两个单链表中,所以创建两个带头结点的单链表M和N,其中M储存原来序号为奇数的元素,N储存原来序号为偶数的元素。
    然后遍历原链表A,当序号为奇数的时候,将对应的元素存储到M,序号为偶数的时候存储到N,添加元素的位置均为链表的末尾。
    要考虑元素之间相对顺序不变,所以还要另外定义两个int变量m和n代表链表M和N的下标,每添加一个元素,下标就加1

  • 代码实现

结点定义

1
2
3
4
5
typedef struct node
{
ElemType element; //结点的数据域
struct node* link; //结点的指针域
}Node;

带头结点的单链表定义,head作为头节点

1
2
3
4
5
typedef struct headerList
{
Node* head;
int n; //单链表的长度n
}HeaderList;

初始化运算:生成空表

1
2
3
4
5
6
7
8
9
10
11
int Init(HeaderList* h)
{
h->head = (Node*)malloc(sizeof(Node)); //生成表头结点

if (!h->head) //表头结点为空返回false
return 0;

h->head->link = NULL;
h->n = 0;
return 1;
}

查找运算:根据下标i查找元素,并通过x返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int Find(HeaderList L, int i, ElemType* x)
{
Node* p;
int j;

if (i < 0 || i > L.n-1) //对i进行越界检查
return 0;

p = L.head->link;
for (j = 0; j < i; j++) //从头结点开始查找下标i的元素
p = p->link;

*x = p->element; //通过x返回元素值(传地址)
return 1;
}

插入运算:根据下标i插入元素x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int Insert(HeaderList* h, int i, ElemType x)
{
Node* p, * q;
int j;

if (i<-1 || i>h->n - 1)
return 0;

p = h->head; //从头结点开始找a(i)元素所在的结点p
for (j = 0; j <= i; j++)
p = p->link;

q = (Node*)malloc(sizeof(Node)); //生成新结点q

q->element = x; //将元素x赋值到新结点q
q->link = p->link; //将新结点q插在p之后
p->link = q;
h->n++;
return 1;
}

输出运算:输出链表的所有元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Output(HeaderList* L)
{
Node* p;

if (!L->n) //判空运算
return 0;

p = L->head->link;
while (p) //依次输出每个元素
{
printf("%d ", p->element);
p = p->link;
}

return 1;
}

主程序步骤
1.先生成原链表A和待存储链表M和N,以及对应的下标m和n
(M储存原来序号为奇数的元素,N储存原来序号为偶数的元素)
2.使用查找运算遍历链表A,用变量x存储返回值
3.用if语句根据下标的奇偶性将a插入链表M和N中
4.当链表A遍历完成后,输出链表A、M和N,验证程序是否有错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int main()
{
HeaderList A, M, N;
int i, m = 0, n = 0;
int x;

Init(&A); //初始化链表
Init(&M);
Init(&N);

for (i = 0; i < 9; i++) //给链表A插入0~9
Insert(&A, i - 1, i);
printf("原List:"); //输出链表A
Output(&A);

for (i = 0; i < 9; i++)
{
Find(A, i, &x); //用查找运算遍历链表A,用x存储返回值
if (i % 2 == 1)
{
Insert(&M, m - 1, x); //下标为奇数时,将元素添加至链表M
m++; //m表示链表M的下标
}
else
{
Insert(&N, n - 1, x); //下标为偶数时,加元素添加至链表N
n++; //n表示链表N的下标
}
}

printf("\nList(奇数下标):");
Output(&M);
printf("\nList(偶数下标):");
Output(&N);
}
  • 输出结果
1
2
3
4
5
6
原List:0 1 2 3 4 5 6 7 8
List(奇数下标):1 3 5 7
List(偶数下标):0 2 4 6 8
E:\Buildings\c\数据结构与算法作业\第二章作业\x64\Debug\第二章作业.exe (进程 12624)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

第二题:设将n(n>1)个整数存放到一维数组R中,设计一个算法,将R中保存的序列循环左移p(O<p<n)个位置,即将R中的数据由(r0, r1,…,rn-1)变换为(rp,rp+1,…,rn-1,r0,r1,…,rp-1)。

首先声明,作者的做法大概率是错的,这一题的做法应该使用循环链表

  • 算法思想

要将n个整数存放到一维数组而且能左移n个位置,就要先定义一个长度至少为2n的数组
因为是将元素左移,所以将元素内容储存在数组下标较大的一端
然后从第一个元素开始遍历数组R,将下标为i的元素和下标为i-p的元素交换
最后输出数组,验证算法是否成功

  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
#define N 14 //n为7,所以数组最大长度N应该至少为14

int main()
{
int n = 7;
int R[N] = { 0 }; //定义一维数组R

for (int i = N - n; i < N; i++) //将n个整数存放到R中
R[i] = i;

printf("数组下标:"); //输出下标
for (int i = 0; i < N; i++)
printf("%d\t", i);

printf("\n\n移动之前:"); //输出移动元素前下标对应的元素
for (int i = 0; i < N; i++)
printf("%d\t", R[i]);

int temp = 0;
int p = 5;
for (int i = N - n; i < N; i++) //从第一个元素开始交换
{
if (p > n || p < 0) //保证0<p<n,超出范围跳出循环
{
printf("不能这么移动!");
break;
}

temp = R[i - p];
R[i - p] = R[i];
R[i] = temp;
}

printf("\n\n移动之后:"); //输出移动元素后下标对应的元素
for (int i = 0; i < N; i++)
printf("%d\t", R[i]);
}
  • 输出结果
1
2
3
4
5
6
7
8
数组下标:0     1       2       3       4       5       6       7       8       9       10      11      12      13

移动之前:0 0 0 0 0 0 0 7 8 9 10 11 12 13

移动之后:0 0 7 8 9 10 11 12 13 0 0 0 0 0
E:\Buildings\c\数据结构与算法作业\第二章作业\x64\Debug\第二章作业.exe (进程 7828)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

使用dd命令提取boot

教程开始

  • 首先在手机上启用开发者选项,并打开以root身份的adb调试

设置导航到关于手机,连续点击版本号直到出现“你已启用开发者选项”
然后找到开发者选项,找到“adb调试”和“以root身份的调试”并打开
如果开发者选项中没有“以root身份的调试”,那么你首先得获取root权限

  • 手机连接电脑,然后在电脑上以打开adb

win+R输入cmd唤出命令提示符,然后输入adb devices来检测设备:

1
2
3
C:\Users\29395>adb devices
List of devices attached
721QAD4S44CXQ device #检测到设备会像这样子显示

要事先将adb所在目录添加到系统变量才能直接在cmd中打开adb,如果没有添加的话必须先定位到adb所在目录

检测到设备后,输入adb root以root身份打开adb

1
2
C:\Users\29395>adb root
restarting adbd as root

如果开发者选项中没有“以root身份的调试”,可以不用做这一步

  • 进入shell,然后找到boot所在的目录

输入adb shell,进入shell

若上一步没有用root身份打开adb,进入shell后输入su就可以让shell获取root权限(前提是你已经root)

然后输入cd /dev/block/by-name进入boot所在目录
再输入ls -l boot查看boot的具体路径

1
2
3
4
5
C:\Users\29395>adb shell
m1721:/ # cd /dev/block/by-name
m1721:/dev/block/by-name # ls -l boot
lrwxrwxrwx 1 root root 21 1970-01-01 09:47 boot -> /dev/block/mmcblk0p21 #这里的/dev/block/mmcblk0p21就是boot的真实路径
m1721:/dev/block/by-name #

现在较新的手机都是Vab分区,所以会存在boot_a和boot_b,按需提取即可

  • 使用dd命令提取boot

复制boot的路径,输入dd if=<复制的boot路径> of=/sdcard/boot.img来提取

1
2
3
4
5
m1721:/dev/block/by-name # dd if=/dev/block/mmcblk0p21 of=/sdcard/boot.img
65536+0 records in
65536+0 records out
33554432 bytes (32 M) copied, 0.625224 s, 51 M/s
m1721:/dev/block/by-name #

这个时候在手机上打开文件管理器,就能在根目录找到刚刚提取的boot.img

使用SakuraFrp实现mc的流畅联机

杂谈

搭建网站之前我有想过把我旧手机拿来当服务器
我还因此了解到内网穿透这东西
发现可以直接用Github直接搭网站之后就放一边了

这两天玩ow给我玩到破防,想着换个游戏玩玩
然后就想到mc联机
之前用Radmin LAN和KatoLAN搞虚拟局域网的效果不好
所以试试内网穿透,暑假的时候同学用过,效果还挺好
想到又可以拉朋(hei)友(nu)一起玩就很爽(

教程开始

首先进入 Sakura Frp官网 然后点击注册账号
注册好之后登入账号,进入管理面板

然后点击用户–实名认证,按步骤进行实名认证(不实名认证无法使用内网穿透)
实名认证名额需要购买,价格为1元

认证完成之后 下载SakuraFrp启动器 并安装
启动器界面应如下图所示

这时回到官网的管理面板,点击用户–个人信息,复制访问密钥,再填入上图启动器中登录

现在来创建隧道
点击服务–隧道列表,再点击创建隧道

节点任意选(可能选离自己所在地更近的节点连接效果更好),然后选择TCP隧道
隧道名随便填
本地ip选择“本地主机(127.0.0.1)”
本地端口选择“[25565]Minecraft Java”
剩下的选项如果你看不懂就不要管,默认会设置好
然后点击“创建”

然后打开mc,开启“对局域网开放”
这时候打开Frp启动器,开启隧道,进入日志界面
隧道会自动检测mc对局域网开放的端口并提示

1
2
3
TCP隧道启动成功
发现 Minecraft 局域网游戏, 本地 IP/端口 已经设置
使用 >>frp-bar.com:xxxxx<< 连接你的隧道

这时候你就可以将上面的链接复制给你的好友让他进入你的房间了

如果有更多问题,参阅官网的 帮助文档

为非gki内核添加KernelSU支持

前言

在Non-gki内核中添加KernelSU的前提得是编译好的内核能够正常开机(废话+1),所以本文也将会详细介绍安卓内核的编译过程以及在编译过程中可能遇到的情况.
​写这个文章不是因为我技术有多好,只是因为我刚开始搞这个跟个伞兵一样找不到路,所以现在稍微明白点了就写下来给想搞这个的看看,也是为了防止我自己以后忘了。。奶子不大好使记不住东西

如果你只是想编译内核而不是使用kernelsu,你可以忽略本文中拉去kernelsu和修改内核配置的部分

环境要求

一台Linux设备(虚拟机,Docker,WSL,甚至是手机上的Terumx都是可以的),我这里以WSL2,Ubuntu 22.04.2 LTS为例

一部可以正常使用的安卓手机(废话+2)

可以让你连接到github和google的方法(你懂的§( ̄▽ ̄)§)

最后再带上你聪明的小脑袋瓜,粗发!

准备工作

  • 首先需要下载所需要的依赖,跟坨屎一样多,直接复制执行就好
1
sudo apt-get install git ccache automake flex lzop bison gperf build-essential zip curl zlib1g-dev g++-multilib libxml2-utils bzip2 libbz2-dev libbz2-1.0 libghc-bzlib-dev squashfs-tools pngcrush schedtool dpkg-dev liblz4-tool make optipng maven libssl-dev pwgen libswitch-perl policycoreutils minicom libxml-sax-base-perl libxml-simple-perl bc libc6-dev-i386 lib32ncurses5-dev libx11-dev lib32z-dev
  • 下载适合自己手机的内核build

这个东西需要自己去Github找,一般为(android)_kernel_<手机厂商>_<cpu代号>

1
2
3
# 以一加8T为例,高通骁龙865代号为sm8250,那么内核仓库名就可能是
android_kernel_oneplus_sm8250
kernel_oneplus_sm8250

我这里的例子是Handelinkernel-v2.6,我在氢的时候最喜欢的一个内核(同时也是最喜欢的一个版本)

1
2
# 仓库地址
https://git.drg.fi/drgreen/android_kernel_oneplus_sm8250.git
  • 下载所需的交叉编译器

​ 由于这个内核自带了编译脚本,所以我们就根据脚本里的的clang版本下载(至于编译脚本我后面再说)
当然,其实我更推荐第三方的clang,因为比较省事比如:ZyC clangproton-clang 或者clang-build-catalogue
使用方法也都差不多,当然你也可以使用谷歌官方的clang不过第三方有的更省事直接用就完事了

1
2
3
# 以我为例,我把它们分别放在了
~/android/clang-r407598b
~/android/toolchain

拉取资源

  • 拉取内核源码
1
2
3
4
5
# cd到用户文件夹
cd ~

# 拉取内核,有的内核还有submodules,所以直接加上--recurse-submodules
git clone --recurse-submodules https://git.drg.fi/drgreen/android_kernel_oneplus_sm8250.git
  • 拉取交叉编译工具
    aosp-clang和第三方clang选其中一个就好
1
2
3
4
5
6
7
8
# aosp-clang

# 新建文件夹让后直接curl
mkdir ~/android
cd ~/android
curl <需要拉取的clang连接>
# 具体版本在这找
# https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+/refs/heads/main

按这个图片找对应版本

找到需要的版本后看下图

1
2
3
4
5
6
7
# 第三方clang(以Zyclang举例),在github仓库的release

# 复制链接直接在当前用户目录下curl
mkdir ~/android
cd ~/android
# 记得选择对应版本
curl <下图获取的连接>

  • 拉取kernelsu
1
2
3
4
cd ~/<内核文件夹>

# 在内核文件夹的根目录执行以下命令
curl -LSs "https://raw.githubusercontent.com/tiann/KernelSU/main/kernel/setup.sh" | bash -s v0.9.5

KernelSU 1.0 及更高版本已经不再支持非 GKI 内核,最后的支持版本为 v0.9.5,请注意使用正确的版本。

修改配置

  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
cd ~/android_kernel_oneplus_sm8250/arch/arm64/configs

# 此时你所需要的配置文件可能在./defconfig
# 但大概率可能会在./vendor/<配置文件>

# 以本内核为例
vi /vendor/kona-perf_defconfig

# 在配置文件最底下加上
CONFIG_KPROBES=y
CONFIG_HAVE_KPROBES=y
CONFIG_KPROBE_EVENTS=y

编译内核

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 进去内核的根目录
cd ~/android_kernel_oneplus_sm8250

# 新建一个shell文件
vim ./build.sh

# 输入以下内容
# shell开始
#!/bin/bash

export ARCH=arm64
export SUBARCH=arm64

args="-j$(nproc --all) \
O=out \
CC=clang \
ARCH=arm64 \
LD=ld.lld \
LLVM=1 \
LLVM_IAS=1 \
CROSS_COMPILE=aarch64-linux-gnu- \
CROSS_COMPILE_COMPAT=arm-linux-gnueabi- " #根据需要可将COMPAT改为ARM32

export PATH="$HOME/<clang文件夹路径>/bin:$PATH"
make ${args} vendor/kona-perf_defconfig
make ${args}
# shell结束

# 保存后退出,给shell执行权限
chmod a+x ./buids.sh
# 执行,用source方便报错后调试
source ./build.sh

试验

如果你足够幸运的话,那么你现在已经成功一半了
去内核文件夹的out/arch/arm64/boot/文件夹看看你的成果
Image放入Anykernel中然后压缩刷入(不同内核压缩方式不同,比如:Image.gz)
Anykernel的使用方法

恭喜你,看来你不是幸运儿

如果你此时将内核刷入后发现各种问题,比如黑屏,重启,高通崩溃(甚至你的out路径里根本就没有出现镜像文件),又或者你编译时报错,所以我将会继续为你排忧解难

  • 如果你刷入内核后手机不能正常启动,你可以需要按照术哥(kernelsu的作者,就是那只皮卡丘)的办法,放弃使用kprobe集成的办法,转去修改那么几个内核文件,具体请参考 https://kernelsu.org/zh_CN/guide/how-to-integrate-for-non-gki.html 中手动修改内核源码部分的内容。在修改完成后再次拉取kernelsu(只是为了执行那个脚本,当然你也可以在内核根文件夹内执行kernelsu的脚本)

版权声明:本文为CSDN博主「果呆皮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:为非gki内核添加KernelSU支持

用gcc编译老内核

准备条件:

  • 一台linux虚拟机(比如我用的Ubuntu 22.04)
  • 一部比较老的测试用设备(比如我手上的米板4)
  • 一个聪明的大脑

安装gcc和各种依赖

依赖部分是我从 另一个教程 里搞来的

1
sudo apt-get install git ccache automake flex lzop bison gperf build-essential zip curl zlib1g-dev g++-multilib libxml2-utils bzip2 libbz2-dev libbz2-1.0 libghc-bzlib-dev squashfs-tools pngcrush schedtool dpkg-dev liblz4-tool make optipng maven libssl-dev pwgen libswitch-perl policycoreutils minicom libxml-sax-base-perl libxml-simple-perl bc libc6-dev-i386 lib32ncurses5-dev libx11-dev lib32z-dev

gcc是编译内核必须的工具链,没有安装gcc就绝对不能编译

安装gcc aarch64和arm32的代码

1
2
3
4
5
#安装gcc aarch64
sudo apt install gcc-aarch64-linux-gnu

#安装gcc arm32
sudo apt install gcc-arm-linux-gnueabi

安装后输出版本号确认

1
2
aarch64-linux-gnu-gcc --version
arm-linux-gnueabi-gcc --version

获取内核源代码

源代码一般在github就能找到
先进入 github 官网,然后在搜索栏中输入android_kernel_(品牌)_(机型),回车搜索

比如小米平板4的品牌是xiaomi,机型是clover:

然后挑选你中意的内核点进去,再点击那个绿色的”Code”按钮复制clone链接
就是那个两个方框连起来那个链接

然后在虚拟机里唤出终端(ctrl + alt + T),输入代码

1
git clone (你复制到的代码)

一切顺利的话,git就会将内核源代码clone到home/android_kernel_(品牌)_(机型)
当然,如果clone过程报错,挂个梯子再试试
如果还是出现问题,问问ai更好

配置编译环境

我试过很多的教程,它们用的都是编译脚本,可是全都失败了
我问deepseek之后才勉强编译出来一个内核(虽然没有开机)
如果之后我用编译脚本成功编译出来内核,我应该会修改这一部分

现在先进入内核根目录吧:

1
cd ~/android_kernel_(品牌)_(机型)

首先按照目标架构选择工具链:

1
2
3
4
5
6
7
# 对于ARM64 (aarch64)
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

# 对于ARM32
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-

然后配置内核

1
make defconfig  # 使用默认配置

开始编译内核

1
make -j$(nproc)  # 使用所有可用核心编译

如果你非常非常幸运,那么这一步你将得到编译完成的内核
一般情况下,编译完成的内核存放在:
~/arch/arm64/boot/Image - 未压缩的内核镜像
~/arch/arm64/boot/Image.gz - 压缩的内核镜像
如果编译的是arm32的内核,镜像文件可能为zImage

但实际上你很不幸,编译过程中出现了很多错误
而且因为报错内容又臭又长,你基本看不懂也不会想去研究
下一篇我就会编译过程中碰到的所有错误和解决办法列出来

可用的替代方案:使用Android预构建工具链

如果系统GCC版本太新,导致出现各种问题,考虑使用Android官方工具链:

1
2
3
4
5
6
# 下载Android NDK
wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip
unzip android-ndk-r21e-linux-x86_64.zip

# 使用NDK工具链
export PATH=$PATH:/path/to/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin

meow~

海内存知己,天涯若比邻