連結串列的應用(資料結構實驗題)
題目:求相鄰k個節點數值之和最大的第一節點
輸入資料(設為整型)建立單鏈表,並求相鄰k個節點data值之和為最大的第一節點。例如輸入k = 2,資料為2 6 4 7 3 #(#為結束符),建立下面連結串列,執行結果輸出(序號3,data值4):
一、 問題分析與演算法思路
首先將問題分解為3個步驟:資料輸入—>資料處理—>結果輸出
題目給定使用連結串列來儲存整型資料,並且只需要求某個節點及其後面k-1個節點的和,所以採用單向非迴圈連結串列實現。
然後分為3個模組來實現:
1、主函式main():
a. 實現資料的輸入。資料可以從鍵盤輸入也可以從.txt檔案輸入。從鍵盤輸入時要檢查結束符“#”,當遇到結束符時結束此次輸入,然後進行資料處理。如果從檔案輸入則首先判斷該檔案是否是.txt格式,如果不是則輸出錯誤提示,選擇重新輸入或者退出程式。如果是.txt檔案則進行檔案讀取,檔案中的整型資料以空格符隔開,遇到結束符“#”時結束讀取,若在遇到結束符之前遇到了非整型資料則輸出錯誤提示,然後選擇重新輸入或者退出程式。
b. 呼叫資料處理模組進行資料處理。輸入資料建立連結串列後讓使用者輸入k的值然後進行求解。
c. 實現資料輸出。輸出當前操作的結果之後繼續輸出提示,讓使用者選擇是繼續對當前連結串列資料求相鄰k個節點data值之和為最大的第一節點還是重新輸入資料求相鄰k個節點data值之和為最大的第一節點,亦或者是結束當前程式。
d. 結束程式。當輸出資料之後,在使用者選擇結束程式後,退出當前程式。
2、建表函式:Creatlist():
建立一個表頭為空的單向非迴圈連結串列,然後將輸入資料依次存入連結串列。
3、求值函式:Adjmax(L, k):
求值函式將從連結串列的第一個元素開始,一直遍歷到第(LisLength(L)- k + 1)個元素,每次遍歷都求出從該元素起到其後第k-1個元素止的k個元素的和,然後比較每個和的大小,返回和最大時的第一節點。求值函式在進行遍歷求和比較時,不是每次都把第一個數與其後共k個數依次相加,而是採用“滑動視窗”式求和。如下圖所示:
當k=3時,從第一個節點data=2開始,計算連續3個節點和,為2+6+4=12。然後“視窗”向右移動一個節點,這時計算“視窗”內資料和時不再是遍歷相加得到6+4+7=17了,而是利用上一次的和12減去上一個“視窗”的第一個數2,然後再加上當前“視窗”的最後一個數7,即12-2+7=17。這樣每次計算和時,只需要做3個數的加減運算,而不再需要將“視窗”內的k個數依次遍歷一遍求和了。
二、程式實現
該程式給出了完整的提示:包括選擇資料輸入方式(鍵盤輸入或者txt檔案輸入)提示,還有對錯誤輸入時的提醒,比如檔案選擇方式錯誤,選擇的檔案型別錯誤等。在程式執行結束後還提供了是否繼續進行操作的選擇。
(該程式在Visual Studio 2015環境下測試通過)
/*
求相鄰k個節點數值之和最大的第一節點
輸入資料(設為整型)建立單鏈表,並求相鄰k個節點data值之和為最大的第一節點。
例如輸入k = 2,資料為2 6 4 7 3 #(#為結束符),建立下面連結串列,執行結果輸出(序號3,data值4)
*/
#include <stdio.h>
#include <string.h>
#define FILEDIR_SIZE 200 //定義要輸入的檔案的目錄的長度
typedef struct node
{
int data;
struct node *next;
}linknode,*link;
void Creatlink(link L); // 連結串列建立函式
void addTolink(link L, int key); // 連結串列新增函式
void Adjmax(link L, int k); //查詢函式
char * right(char *dst, char *src); //字串擷取函式,用於判斷輸入的檔案是否是txt型別
void release(link L);
int length = 0; //連結串列的長度,用於判斷k是否合法
void main()
{
int k = 0;
link List ;
int sel=1; //選擇是否繼續
List = (link)malloc(sizeof(linknode));
List->next = NULL;
while (sel==1)
{
int repeat = 1;
release(List); //連結串列建立之前先釋放
Creatlink(List);
while (repeat == 1)
{
printf_s("Please input k:");
scanf_s("%d", &k);
Adjmax(List, k);
printf_s("Continue to input k(input 1) or Exit(input 0):\n");
scanf_s("%d", &repeat);
}
printf_s("Continue to input the link data(input 1) or Exit(input 0):\n");
scanf_s("%d", &sel);
}
}
void Creatlink(link L) //建立連結串列函式,資料可選擇從鍵盤輸入或者從檔案讀入,可複用
{
char in[10]; //定義一個字元陣列來儲存輸入的值
char fsuffix[4] = ""; //存放檔案字尾,判斷是否是txt型別
printf("-----Input from the keyboard(input \"K\") or the file(input \"F\")-----\n"); //提示使用者:輸入K代表從鍵盤輸入資料,輸入F則是從檔案讀取資料
scanf_s("%s", in, 10);
while (in[0] != 'K' && in[0] != 'F') //如果使用者輸入的既不是K也不是F則提示使用者重新輸入
{
printf("Illegal input !!! please choose again......\n");
scanf_s("%s", in, 10);
}
if (in[0] == 'F') //如果選擇的是檔案輸入
{
printf("Please input the absolute path of a txt file:\n");
FILE *fp = NULL; //儲存使用者指定輸入的檔案
char fileDir[FILEDIR_SIZE];
scanf_s("%s", fileDir, FILEDIR_SIZE);
errno_t err;
while ((err = fopen_s(&fp, fileDir, "r")) != 0 || strcmp(right(fsuffix, fileDir), "txt") != 0) //如果開啟不成功,或者開啟的不是txt格式檔案
{
printf("The file can't be found or the file type can't be allowed, please input again:\n");
scanf_s("%s", fileDir, FILEDIR_SIZE);
}
int y = 0;
while (EOF != fscanf_s(fp, "%d", &y))
{
addTolink(L, y);
}
}
else if (in[0] == 'K')
{
printf("please start to input the link data, end with '#':\n");
int x=0;
char c='!';
while (c != '#')
{
if(!scanf_s("%d", &x))
scanf_s("%c", &c);
else
addTolink(L, x);
}
}
}
void addTolink(link L, int key)
{
link p=L, r;
r = (link)malloc(sizeof(linknode));
r->data = key;
r->next = NULL;
while (p->next)
{
p = p->next;
}
p->next = r;
length++;
}
void Adjmax(link L, int k)
{
if (!(k < length+1 && k>0))
{
printf_s("The k is illegal, please try again\n");
return;
}
link result=NULL,r,p = L, q = L->next; //result用於存放和最大的第一節點,r用於遍歷求和
int sum = -1; //初始值為-1,防止result為空指標引發異常
int sumt = 0; //存放臨時的和
int order = 0;
int ordert=0; //記錄臨時序號
for (int i = 0; i < k; i++) //使p指向以q開頭的連續k個節點序列的最後一個節點。q、p分別為“視窗”開始和結束
{
if(p->next)
p = p->next;
}
while (p)
{
ordert++;
if (ordert > 1)
{
sumt = sumt + p->data - q->data; //滑動視窗法計算和
q = q->next;
}
else
{
r = q;
for (int j = 0; j < k; j++) //計算連續k個數的和
{
sumt += r->data;
r = r->next;
}
}
if (sumt > sum)
{
sum = sumt;
result = q;
order = ordert;
}
p = p->next; //先只移動尾指標p
}
printf_s("序號%d,data值%d\n",order,result->data);
}
void release(link L) //連結串列釋放函式
{
if (L->next)
{
link q = L->next, p = L->next;
while (q)
{
p = q;
q = q->next;
free(p);
}
}
L->next = NULL;
}
char * right(char *dst, char *src) //擷取src的最後3位賦值給dst,用於判斷檔名
{
int n = 3;
char *p = src;
char *q = dst;
int len = strlen(src);
if (n>len) n = len;
p += (len - n); //從右邊第n個字元開始,到0結束
while (*(q++) = *(p++));
return dst;
}
/*
void Adjmax(link L, int k) //該函式執行時需註釋掉,這個為普通演算法,上面的為滑動視窗法
{
if (!(k < length + 1 && k>0))
{
printf_s("The k is illegal, please try again\n");
return;
}
link result = NULL, r, p = L, q = L->next; //result用於存放和最大的第一節點,r用於遍歷求和
int sum = 0;
int sumt = 0; //存放臨時的和
int order = 0;
int ordert = 0; //記錄臨時序號
for (int i = 0; i < k; i++) //使p指向以q開頭的連續k個節點序列的最後一個節點。q、p分別為“視窗”開始和結束
{
if (p->next)
p = p->next;
}
while (p)
{
ordert++;
{
r = q;
sumt = 0;
for (int j = 0; j < k; j++) //計算連續k個數的和
{
sumt += r->data;
r = r->next;
}
}
if (sumt > sum)
{
sum = sumt;
result = q;
order = ordert;
}
p = p->next; //向後移動尾指標p和q
q = q->next;
}
printf_s("序號%d,data值%d\n", order, result->data);
}
*/
三、結果分析
1、測試思路:先選用鍵盤輸入方式,然後開始輸入一串資料,#結束,然後輸入k的值,正確輸出結果。然後選擇仍用改組資料,但是用新的k進行計算。
當新的k小於(在此為0)或大於邊界(該測試是6,資料長度為5)時,提示錯誤,然後選擇重新輸入,輸入正確時正確輸出。
然後不退出程式,選擇重新從鍵盤輸入測試資料。這時連結串列會進行清空重置。接下來的測試資料只有一個數,k只能為1,結果正確。
若測試輸入的資料為0個,則k無有效值。
2、演算法和結果的有效性分析
由普通演算法實現所用平均時間約為5秒(資料規模為50000+,測試檔案為138kB的txt檔案。)
普通演算法的計算函式程式碼如下:
void Adjmax(link L, int k)
{
if (!(k < length + 1 && k>0))
{
printf_s("The k is illegal, please try again\n");
return;
}
link result = NULL, r, p = L, q = L->next; //result用於存放和最大的第一節點,r用於遍歷求和
int sum = 0;
int sumt = 0; //存放臨時的和
int order = 0;
int ordert = 0; //記錄臨時序號
for (int i = 0; i < k; i++) //使p指向以q開頭的連續k個節點序列的最後一個節點。q、p分別為“視窗”開始和結束
{
if (p->next)
p = p->next;
}
while (p)
{
ordert++;
{
r = q;
sumt = 0;
for (int j = 0; j < k; j++) //計算連續k個數的和
{
sumt += r->data;
r = r->next;
}
}
if (sumt > sum)
{
sum = sumt;
result = q;
order = ordert;
}
p = p->next; //向後移動尾指標p和q
q = q->next;
}
printf_s("序號%d,data值%d\n", order, result->data);
}
該演算法的時間複雜度為O(n2)
若使用“滑動視窗”演算法實現,平均使用時間約為<0.1秒。(沒有測試更大資料集,因為大資料集會導致建立連結串列時間過長從而無法實現。)該演算法程式碼見程式實現部分的void Adjmax(link L, int k) 函式。
該函式的時間複雜度為O(n)。
最後說一下,這個題目是大學的資料結構實驗題目,老師檢查還是有點嚴的,如果拿去參考還是得自己真的明白。總共有六題,這是第一題,我覺得這個做的比較好,所以先分享出來。得分都是A+的哦。