1. 程式人生 > >連結串列的應用(資料結構實驗題)

連結串列的應用(資料結構實驗題)

題目:求相鄰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+的哦。