# 程式設計Ⅱ【筆記Ⅰ】
* [name=Ivy Lin]
* [筆記Ⅱ](https://hackmd.io/ukBiB1VJTB-T5T4c_uAA3g)
* [筆記ⅠⅠⅠ](https://hackmd.io/JpCPopTTTLyZ91yi279ewA)
* [//筆記Ⅳ](https://hackmd.io/Ts0lnTMpQgGPGw7cvc3X-A)
## 第一週 <*2023.9.12*>
> 利用指標將資料串接形成 ***Linked list*** 結構
### 標準型式 ***singly linked list***
* linked list的結尾用 **NULL** 表示
#### 作法
1. 定義底層資料型態(通常使用 **struct** 搭配 **typedef**)
```clike=
typedef struct t_list{
//要儲存的資料
int id;
char str[10];
//指向LIST結構的指標
struct t_list* next;
}List; //簡化的名字
```
2. 思考需要被串接的函數內容
```clike=
List* getData(void); //一個List結構的函數,用來取出儲存在結構中的資料)
List* addToLast(List* head, List* np); //在既有的 linked list 加入資料
List* removeFirst(List* head); //在既有的 linked list 移除資料
void showList(List* lst); //顯示list
List* freeList(List* lst); //free list所使用的記憶體
```
3. 讀取資料並串接
> "->" 將輸入的值放到結構指標的某個結構元素中
> "." 結構陣列用此來存取值
> "a->b" 和 "(*a).b" 語法相同
```clike=
List* getData(void){
List* np;
static int ID; //讓ID的值不消失並存放在getData中
np = (List *)malloc(sizeof(List)); //利用malloc創建一個List的空間
if(np!=NULL){
printf("Enter a name: ");
if (scanf("%9s", np->str)==1) { //利用->將輸入的值存到str中,"==1"是確認使用者確實輸入了可以被讀取的值
np->id = ID++;
np->next = NULL;
} else {
free(np); //歸還剛剛建構的記憶體空間
np = NULL; //並重新將np設成NULL
}
}
return np;
}
```
4. 在既有的 linked list 加入或移除資料
> 增加或移除資料的方式依照list函數的操作有所差異
> 通常會使用list的結尾做操作(稱作Quene)
```clike=
//新增、移除資料的函數宣告
List* addToLast(List* head, List* np);
List* removeFirst(List* head);
//函數實作
//新增資料函數 addToLast
List* addToLast(List* head, List* np){ //head指向
List* ptr = head;
if(head == NULL)
head = np; //若head為空,則將head指向np(意即np為第一筆資料)
else{
while(ptr->next != NULL)
ptr = ptr->next; //利用迴圈走到資料的最後一格
ptr->next = np; //插入新的資料
}
return head; //回傳資料開頭
}
//刪除資料函數 removeFirst
List* removeFirst(List* head){
List* ptr;
if(head == NULL) return NULL;
else{
ptr = head->next; //用ptr記住下一筆資料
free(head); //free上一筆資料
return ptr; //回傳下一筆資料的位置
}
}
```
5. 顯示linked list的內容
> 關鍵句: ***lst = lst->next;***
```clike=
void showList(List* lst){
printf("[");
while(lst != NULL){
printf("%d:%s,",lst->id,lst->str);
lst=lst->next;
}
printf("]\n");
}
```
6. 清除整個linked list
> 利用寫好的removeFirst
```clike=
List* freeList(List* lst){
while(lst != NULL){
lst = removeFirst(lst);
}
return NULL;
}
```
7. main函數(讀取資料)
> 在main的開頭 ***List* head = NULL;***
> 一定要記得設定初值NULL,因為之後呼叫 addToLast 會來當作判斷依據
```clike=
int main(){
List* head = NULL; //務必要設初始直為NULL
List* np = NULL;
while((np = getData()) != NULL) {
head = addToLast(head, np);
showList(head);
}
showList(head);
head = removeFirst(head); //把第一筆資料移除,並更新head指標
showList(head);
head = freeList(head); //清空list
return 0;
}
```
### 環狀型式 ***Circular Linked List***
* 或是將最後一筆資料接回開頭形成環狀結構
#### 作法
1. 定義底層資料型態(多增加sentinel node來指向第一筆和最後一筆資料)
```clike=
typedef struct t_List {
int id;
char str[10];
struct t_List* next;
} List;
//同原本的linked list
typedef struct {
List* first;
List* last;
} Head;
```
2. 思考需要被串接的函數內容
```clike=
List* getData(void); //一個List結構的函數,用來取出儲存在結構中的資料)
Head addToLast(Head head, List* np); //在既有的 linked list 加入資料
Head removeFirst(Head head); //在既有的 linked list 加入資料
void showList(Head head); //顯示list
Head freeList(Head head); //free list所使用的記憶體
```
3. 讀取資料並串接
> 和single list的不同是=>原本np->next是指向NULL
> 環狀list會改成指向自己本身
```clike=
List* getData(void)
{
List* np;
static int id;
np = (List *) malloc(sizeof(List));
if (np!=NULL) {
printf("Enter a name: ");
if (scanf("%9s", np->str)==1) {
np->id = id++;
np->next = np; // 這一行不一樣,指向自己而不是NULL
} else {
free(np);
np = NULL;
}
}
return np;
}
```
4. 在既有的 linked list 加入或移除資料
> 若傳入值為NULL,將np設為第一筆資料
> 否則找到最後一筆資料並把np接在後面
> 要維持環狀結構,np->next要指回第一筆資料
```clike=
//函數實作
//新增資料函數 addToLast
Head addToLast(Head head, List* np)
{
if (head.last == NULL) {
head.first = np;
head.last = np;
} else {
np->next = head.first;
(head.last)->next = np;
head.last = np;
}
return head;
}
```
> 若 head.first 是NULL => return head;
> 若 head.first==head.last => 代表只有一筆資料 => free(head.first); **記得要把head.first/last設成NULL**
> 若有超過一筆資料 =>
> 1. 先將last_next 指向first_next
> 2. free(head.first)
> 3. 將head.first指向last_next
```clike=
//刪除資料函數 removeFirst
Head removeFirst(Head head)
{
if (head.first != NULL) {
if (head.first == head.last) {
free(head.first);
head.first = head.last = NULL;
} else {
(head.last)->next = (head.first)->next;
free(head.first);
head.first = (head.last)->next;
}
}
return head;
}
```
5. 顯示linked list的內容
> 關鍵句: ***lst = lst->next;***
```clike=
void showList(Head head)
{
List* lst = head.first;
if (lst==NULL) printf("[]\n");
else {
printf("[");
do {
printf("%d:%s,", lst->id, lst->str);
lst = lst->next;
} while (lst != head.first);
printf("]\n");
}
}
```
6. 清除整個linked list
> 利用寫好的removeFirst
```clike=
Head freeList(Head head)
{
while (head.first != NULL) {
head = removeFirst(head);
}
return head;
}
```
7. main函數(讀取資料)
> 注意到 Head head;是一個一般的變數而不是指標
> head 包含的兩個欄位 head.first 和 head.last 都是指向 List 的指標
> 必須手動把兩個指標都設定為 NULL
```clike=
int main(void)
{
Head head = {NULL, NULL};
List* np = NULL;
//head.first = head.last = NULL;
while((np = getData()) != NULL) {
head = addToLast(head, np);
showList(head);
}
showList(head);
head = removeFirst(head);
showList(head);
head = freeList(head);
return 0;
}
```
### 將資料和linked list分開儲存
```clike=
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
char str[10];
} Node;
typedef struct t_List {
Node* data; // 透過指標,記住資料所在位址
struct t_List* next;
} List;
Node* createNode(void); // 用來產生資料
List* cons(Node* nodep, List* lst); // cons 將資料加在 List 最前面 cons head tail
void showList(List* lst);
void freeList(List* lst);
Node* head(List* lst); // 取得 List 第一筆資料
List* tail(List* lst); // 取得扣除第一筆資料之後 剩下的 List
int main(void)
{
Node* np = NULL;
List* lst = NULL;
while((np = createNode()) != NULL) {
lst = cons(np, lst); // 不斷用 cons 把新讀取的資料加在既有的 List 前面
showList(lst);
}
showList(lst);
printf("%d: %s\n", head(lst)->id, head(lst)->str);
showList(tail(lst));
freeList(lst);
return 0;
}
Node* createNode(void)
{
Node* nodep;
static int id;
nodep = (Node *) malloc(sizeof(Node));
if (nodep!=NULL) {
printf("Enter a name: ");
if (scanf("%9s", nodep->str)==1) {
nodep->id = id++;
} else {
free(nodep);
nodep = NULL;
}
}
return nodep;
}
List* cons(Node* nodep, List* lst)
{
List* hp;
if (nodep==NULL) return lst;
else {
hp = (List*) malloc(sizeof(List)); // 產生一個 List 結構 並且用指標 hp 記住位址
hp->data = nodep; // 把其中的 data 指標指向 nodep 這筆資料
hp->next = lst; // 把 next 指標指向 既有的 lst
return hp; // 把 hp 所記住的位址傳回去
}
}
void showList(List *lst)
{
printf("[");
while (lst != NULL) {
printf("%d:%s,", lst->data->id, lst->data->str);
lst = lst->next;
}
printf("]\n");
}
void freeListHelper1(List* lst)
{
while (lst != NULL) {
if (lst->data != NULL) {
free(lst->data);
lst->data = NULL;
}
lst = lst->next;
}
}
void freeListHelper2(List* lst)
{
if (lst == NULL) return;
else {
freeListHelper2(lst->next);
free(lst);
}
}
void freeList(List* lst)
{
freeListHelper1(lst); // free 要分成兩步驟, 先把 List 裡面記住的每筆資料清除
freeListHelper2(lst); // 再把 List 本身清除
}
Node* head(List *lst)
{
if (lst != NULL)
return lst->data;
else
return NULL;
}
List* tail(List *lst)
{
if (lst != NULL)
return lst->next;
else
return NULL;
}
```
## 第二週 <*2023.9.19*>
> 透過參數,傳遞某個指標變數的位址,以便修改函數外部的指標變數的 ***Linked list*** 結構
### 補充寫法(與作業相關)
> [補充資料連結](https://github.com/htchen/i2p-nthu/blob/master/%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88%E4%BA%8C/mid1/2-linked_list_sup.md)
:::info
* 寫法差異:
主要是更改在傳入變數時,改為傳入指標變數的地址(不須複製一份而是直接更改值)
* 實作:
```clike=
void insert_increase_list(Node**, int);
```
:::
```clike=
#include <stdio.h>
#include <stdlib.h>
// struct 結構
typedef struct _Node
{
// 存放的資料
int data;
// struct型態 名字為_Node型別的指標
struct _Node *next;
} Node;
void insert_increase_list(Node **, int);
void show_list(Node *);
void delete_node(Node **, int);
Node *create_node(int x)
{
Node *n = (Node *)malloc(sizeof(Node));
n->data = x;
n->next = NULL;
return n;
}
void insert_increase_list(Node **hp, int x)
{
// Node *n = create_node(x);
if (*hp == NULL)
*hp = create_node(x);
else
{
Node *q;
Node *prev = NULL; // 為了記住前一個指標
q = *hp;
while (q != NULL && q->data <= x)
{
prev = q;
q = q->next;
}
if (prev == NULL) // 插在開頭的情況
{
prev = create_node(x);
prev->next = *hp;
*hp = prev;
}
else // 其他情況(debug時要注意頭尾的狀況是否也不會出錯)
{
prev->next = create_node(x);
prev->next->next = q;
}
}
}
void show_list(Node *p)
{
while (p != NULL)
{
printf("{%d,next--}-->", p->data);
p = p->next;
}
}
void delete_node(Node **hp, int x)
{
if (*hp == NULL) // 空list=>不做任何動作
return;
else // 有資料的list
{
Node *q;
q = *hp;
Node *prev = NULL;
// 若q不是NULL且data不是我要的資料=>繼續找下一個
while (q != NULL && q->data != x)
{
prev = q;
q = q->next;
}
// 如果q=NULL => 完全沒找到要的 => 直接return
if (q == NULL)
return;
// 如果prev=NULL => 資料在到最前端 => *hp設成q->next
if (prev == NULL)
{
*hp = q->next;
free(q);
// 若找到了的解法
else
{
prev->next = q->next;
free(q);
}
}
}
int main()
{
int x;
Node *head = NULL;
while (scanf(" %d", &x) == 1)
insert_increase_list(&head, x);
show_list(head);
while (getchar() != '\n')
;
scanf(" %d", &x);
printf("%d\n", x);
delete_node(&head, x);
// printf("k=%d\n", k);
show_list(head);
return 0;
}
```
### 兩個星號的練習
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定義結構=> str紀錄一個動態陣列
typedef struct
{
int data;
char *str;
} Node;
// 簡單建構創造新結構的動態陣列方式
Node *create_node()
{
int x;
char s[258];
scanf("%d", &x);
scanf("%s", s);
// 動態創造一個結構的記憶體
Node *n = (Node *)malloc(sizeof(Node));
n->data = x;
// 計算輸入的資料長度後在記憶體挖一樣大小的空間(要多一格放'\0')
n->str = (char *)malloc(sizeof(char) * ((strlen(s) + 1)));
// 複製s的資料到n->str開出的記憶體空間
strcpy(n->str, s);
return n;
}
void show(Node *p)
{
printf("%p,%d,%s\n", p, p->data, p->str);
}
// 兩顆星的實作處
void swp(Node **a, Node **b)
{
// 為了能直接改動函數外的值=>傳入指標的位置(雙星=>指標的位置)
// 創造一個新的結構變數(暫存資料)
Node *x;
// 結構變數x(位置)=*a(取得位置的值=>原本資料存放的地址)
x = *a;
*a = *b;
*b = x;
/*
**a(指標的位置) -> *a(指標) -> a(資料)
**b(指標的位置) -> *b(指標) -> b(資料)
利用新的x指標來暫存 *a, 對整個指標交換(重抄地址)
**a(指標的位置) -> *b(指標) -> b(資料)
**b(指標的位置) -> *a(指標) -> a(資料)
*/
}
int main()
{
Node *s1, *s2;
s1 = create_node();
s2 = create_node();
// printf("{**s1,%p},{*s1,%p}\n", &s1, s1);
// printf("{**s2,%p},{*s2,%p}\n", &s2, s2);
show(s1);
show(s2);
swp(&s1, &s2);
// printf("{**s1,%p},{*s1,%p}\n", &s1, s1);
// printf("{**s2,%p},{*s2,%p}\n", &s2, s2);
show(s1);
show(s2);
}
```
### Linus 指標的優化寫法
:::info
Linus Torvalds on understanding pointers
範例:[https://grisha.org/blog/2013/04/02/linus-on-understanding-pointers/](https://grisha.org/blog/2013/04/02/linus-on-understanding-pointers/)
:::
* 在找尋資料的過程中,通常會多一個if-else來挑掉不合的狀況
```clike=
//以上面的delete程式碼舉例
void delete_node(Node **hp, int x)
{
if (*hp == NULL)
return;
else
{
Node *q;
q = *hp;
Node *prev = NULL;
while (q != NULL && q->data != x)
{
prev = q;
q = q->next;
}
if (q == NULL)
return;
//這個if-else被視為是不必要的
if (prev == NULL)
{
*hp = q->next;
free(q);
else
{
prev->next = q->next;
free(q);
}
}
}
```
* 老手的寫法(省掉多餘的if-else)
```clike=
//改寫前面的例子
void delete_node(Node **hp, int x)
{
if (*hp == NULL) return;
else
{
Node *q;
//兩個星號的東西=>hp
//用q來記住hp位置(link list頭)
q = *hp;
while(q){ //若q!=NULL(從頭找到尾)
if (q->data == x){
//將hp位置設成data的next(接在下一個指標上)
*hp = q->next;
free(q);
q = (*hp);
}
//更新資料
//hp(指標的位址)設為 原本記憶data->next的位址
hp = &(q->next);
//q設成hp的值
q = (*hp);
}
}
}
```
## 第三週 <*2023.9.26*>
> 演算法、約瑟夫斯問題、資料結構
### ***Josephus Problem***
> ***Problem Definition***
> 有 n 個人排成一圈,從 第一個位置開始數,往前每數到第 m 個人,就將那個人從圈子中移除。
> 將被移除的順位列出,就構成所謂的 Josephus Permutation。
> [參考連結](https://en.wikipedia.org/wiki/Josephus_problem)
#### 解法1
> 產生陣列,元素即為每個人的編號
> 逐步模擬移除的過程,移除後將後面陣列元素往前挪
```clike=
#include <stdio.h>
#include <stdlib.h>
int main()
{
int n, m, count = 0, pos = 0;
int *a;
scanf("%d %d", &n, &m);
a = (int *)malloc(sizeof(int) * n);
for (int i = 0; i < n; i++)
a[i] = i + 1;
while (n > 1)
{
pos += (m - 1);
pos %= n;
// a[pos] = -1;
for (int i = pos + 1; i < n; i++)
a[i - 1] = a[i];
--n;
}
printf("%d", a[0]);
return 0;
}
```
:::warning
* 複雜度
兩層迴圈 => Big-O(n^2)
:::
#### 解法2
> 產生陣列,不搬運,跳過被算過的人
```clike=
#include <stdio.h>
#include <stdlib.h>
int main()
{
int n, m, count = 0;
scanf("%d %d", &n, &m);
int *a;
a = (int *)malloc(sizeof(int) * (n + 1));
for (int i = 0; i < n; i++)
a[i] = i++;
int pos = 0, check;
while (count < n)
{
check = 0;
while (check < (m % n))
{
pos += 1;
pos %= n;
if (a[pos] != -1)
check++;
// printf("{pos,check}={%d,%d}\n", pos, check);
}
a[pos] = -1;
//重點是輸出這行!!!!!
//(原本的結果會使程式正確運作,但顯示的數值會和正確數值差一格)
printf("pos=%d\n", (pos + n - 1) % n);
count++;
}
return 0;
}
```
#### 解法3
> 用link list的方式串接資料
> 使用串接資料的方式將要被刪除的元素列印出來
```clike=
#include <stdio.h>
#include <stdlib.h>
typedef struct _Node
{
int id;
struct _Node *next;
} Node;
typedef struct
{
Node *first;
Node *last;
} Head;
Node *create_Node(int id)
{
Node *p;
p = (Node *)malloc(sizeof(Node));
p->id = id;
p->next = p;
return p;
}
Head insert_Node(Head h, Node *p)
{
if (h.first == NULL)
h.first = h.last = p;
else
{
p->next = h.first;
h.first = p;
(h.last)->next = h.first;
}
return h;
}
void show(Head h)
{
Node *p;
for (p = h.first; p->next != h.first; p = p->next)
{
if (p != NULL)
printf("%d->", p->id);
}
printf("%d->", p->id);
printf("%d\n", (p->next)->id);
// printf("%d\n", p->id);
}
Head delete_node(Head h, Node *prev)
{
Node *p;
if (h.first != NULL) // 有資料的link list
{
// 只有一個資料
if (h.first == h.last && h.first == prev)
{
// 更新head資料為NULL
h.first = h.last = NULL;
free(prev);
}
// 超過一筆資料
else
{
// 記住要free掉的元素
p = prev->next;
// free掉頭的情況
if (p == h.first)
{
// 頭指標改成頭的下一位
h.first = p->next;
// 尾指標改成更新過的頭座標
(h.last)->next = h.first;
}
// free掉尾的情況
else if (p == h.last)
{
// 尾指標改為prev(原尾巴的前一個元素)
h.last = prev;
// 尾指標的next要改成指向頭指標
(h.last)->next = h.first;
}
// 其餘情況
else
// 將prev的next指向下下個元素
prev->next = p->next;
free(p);
}
}
return h;
}
int main()
{
int n, m;
scanf("%d %d", &n, &m);
Head head = {NULL, NULL};
Node *p;
int *a;
a = (int *)malloc(sizeof(int) * n);
for (int i = n; i > 0; i--)
{
p = create_Node(i);
head = insert_Node(head, p);
}
show(head);
int check, count = 0;
p = head.last;
while (count < n)
{
check = 0;
while (check < ((m - 1) % n))
{
p = p->next;
check++;
}
// 一樣對輸出的值做改動
printf("%d->", ((p->next)->id) - 1);
head = delete_node(head, p);
count++;
}
return 0;
}
```
#### 解法4
> 用link list記住下一個還沒被殺死的元素
> 直接走到新的格子(但沒有free掉被殺死的元素)
```clike=
#include <stdio.h>
#include <stdlib.h>
typedef struct _Node
{
int id;
int next;
int prev;
} Node;
int main()
{
int n, m;
scanf("%d %d", &n, &m);
Node *a;
a = (Node *)malloc(sizeof(Node) * n);
for (int i = 0; i < n; i++)
{
a[i].id = i + 1;
a[i].next = (i + 1) % n;
a[i].prev = (i - 1) % n;
}
int check, count = 0, p, kill;
p = n - 1;
while (count < n)
{
check = 0;
while (check < ((m - 1) % n))
{
p = a[p].next;
check++;
}
// 一樣對輸出的值做改動
kill = a[p].next;
printf("%d->", (a[kill].id) - 1);
a[p].next = a[kill].next;
count++;
}
}
```
#### 解法5(下星期會再詳細的講解)
> 使用遞迴方式來計算數值
> 優點:不用寫複雜的link也不須模擬
> 缺點:過程無法顯示,只能計算出最後剩下的元素
```clike=
#include <stdio.h>
int f(int n, int m){ // index from 1 to n
//公式本人
if( n == 1 ) return 1;
else return ( f(n-1, m) + m-1 ) % n + 1;
}
int main(void) {
int n = 41, m = 3, i;
printf("%d\n", f(n,m));
return 0;
}
```