數據結構與算法之線性表單向循環鏈表


在上一篇《數據結構與算法之基礎篇》中,瞭解了算法和數據結構的一些概念問題,以及線性表順序存儲單鏈表的創建、插入、刪除等一些操作。這一篇來了解一下線性表單向循環鏈表的一些操作。

 

1.  單向循環鏈表概念

1.1 單向循環鏈表

是單鏈表的一個變形,鏈表中最後一個節點的next域不再爲None,而是指向鏈表的頭節點。


1.2 鏈表結構和順序存儲的優缺點:

存儲分配方式

  • 順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素;
  • 單鏈表採用鏈式存儲結構,⽤用⼀一組任意的存儲單元存放線性表的元素

時間性能

  • 查找

   順序存儲 O(1)

   單鏈表O(n)

  • 插入和刪除

   存儲結構需要平均移動一個表長一半的元素,時間O(n)

   單鏈表查找某位置後的指針後,插⼊和刪除爲 O(1)

空間性能

  • 順序存儲結構需要預先分配存儲空間,分太⼤大,浪費空間;分⼩小了了,發⽣生上溢出;
  • 單鏈表不不需要分配存儲空間,只要有就可以分配, 元素個數也不不受限制;

2. 單向循環鏈表的操作

準備工作:定義相關變量

#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1

#define MAXSIZE 20 /* 存儲空間初始分配量 */

typedef int Status;/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定,這裏假設爲int */

//定義結點
typedef struct Node{
    ElemType data;
    struct Node *next;
}Node;

typedef struct Node * LinkList;

2.1. 單向循環鏈表的創建

單向循環鏈表的創建分爲2種情況:

  • 第一次開始創建;
  • 已經創建,往裏面新增數據

判斷是否第一次創建鏈表

  • YES: 創建一個新結點,並使得新結點的next指向自身, (*L)->next = (*L);
  • NO : 找鏈表尾結點,將尾結點的next = 新結點新結點的next = (*L);

而鏈表找尾,可以有兩種寫法,一種爲for循環,一種爲定義一個變量記錄。

創建代碼示例:

// for循環找尾
Status CreateList(LinkList *L){

    int item;
    LinkList temp = NULL;
    LinkList target = NULL;
    printf("輸入節點的值,輸入0結束\n");
    while(1)
    {
        scanf("%d",&item);
        if(item==0) break;
        
          //如果輸入的鏈表是空。則創建一個新的節點,使其next指針指向自己  (*head)->next=*head;
        if(*L==NULL)
        {
            *L = (LinkList)malloc(sizeof(Node));
            if(!L)exit(0);
            (*L)->data=item;
            (*L)->next=*L;
        }
        else
        {
           //輸入的鏈表不是空的,尋找鏈表的尾節點(for循環找尾),使尾節點的next=新節點。新節點的next指向頭節點
            for (target = *L; target->next != *L; target = target->next);
            
            temp=(LinkList)malloc(sizeof(Node));
            
            if(!temp) return ERROR;
            
            temp->data=item;
            temp->next=*L;  //新節點指向頭節點
            target->next=temp;//尾節點指向新節點
        }
    }
    
    return OK;
}
// 定義變量 r, 記錄尾
Status CreateList2(LinkList *L){
    
    int item;
    LinkList temp = NULL;
    LinkList target = NULL;
    LinkList r = NULL;
    printf("請輸入新的結點, 當輸入0時結束!\n");
    while (1) {
        scanf("%d",&item);
        if (item == 0) {
            break;
        }
        //第一次創建
        if(*L == NULL){
            *L = (LinkList)malloc(sizeof(Node));
            if(!*L) return ERROR;
            (*L)->data = item;
            (*L)->next = *L;
            r = *L;
        }else{
            
            temp = (LinkList)malloc(sizeof(Node));
            if(!temp) return  ERROR;
            temp->data = item;
            temp->next = *L;
            r->next = temp;
            // 記錄尾
            r = temp;
        }
        
    }
    return OK;
}

2.2. 單向循環鏈表的遍歷

頭節點就有值,可以用do...while

void traverseLinkList(LinkList p)
{
    //如果鏈表是空
    if(p == NULL){
        printf("打印的鏈表爲空!\n");
        return;
    }else{
        LinkList temp;
        temp = p;
        do{
            printf("%3d",temp->data);
            temp = temp->next;
        }while (temp != p);
        printf("\n");
    }
    
}

2.3. 單向循環鏈表的插入

單向循環列表的插入分爲兩種情況:

  1.  插入位置在首元節點(特殊處理)
    • 創建新節點temp,並判斷是否創建成功,成功則賦值
    • 找到鏈接最後的尾結點,即找尾。
    • 新節點tempnext->頭結點
    • 尾結點的next 指向新的頭結點
    • 讓頭指針指向temp(臨時的新結點)


2.  插入位置在其他位置
* 創建新節點temp,並判斷是否創建成功,成功則賦值
* 找到插入的位置,如果超過鏈表長度,則自動插入隊尾
* 通過target找到要插入位置的前一個結點(即插入結點的前驅), 讓target->next = temp
* 插入結點的前驅的next指向新結點,新結點的next 指向target原來的next位置

//4.3 循環鏈表插入數據
Status ListInsert(LinkList *L, int place, int num){
    
    LinkList temp ,target;
    int i;
    if (place == 1) {  //如果插入的位置爲1, 特殊處理
    
        //1. 創建新結點temp,並判斷是否創建成功,成功則賦值,否則返回ERROR;
        temp = (LinkList)malloc(sizeof(Node));
        if (temp == NULL) {
            return ERROR;
        }
        temp->data = num;
        
        //2. 找到鏈表最後的結點_尾結點,
        for (target = *L; target->next != *L; target = target->next);
        //3. 讓新結點的next 指向頭結點.
        temp->next = *L;
        //4. 尾結點的next 指向新的頭結點;
        target->next = temp;
        //5. 讓頭指針指向temp(臨時的新結點)
        *L = temp;
        
    }else { //如果插入的位置在其他位置;

        //1. 創建新結點temp,並判斷是否創建成功,成功則賦值,否則返回ERROR;
        temp = (LinkList)malloc(sizeof(Node));
        if (temp == NULL) {
            return ERROR;
        }
        temp->data = num;
        
        //2. 先找到插入的位置,如果超過鏈表長度,則自動插入隊尾;
        for ( i = 1,target = *L; target->next != *L && i != place - 1; target = target->next,i++) ;
        
        //3. 新結點的next 指向target原來的next位置;
        temp->next = target->next;
        //4. 通過target找到要插入位置的前一個結點, 讓target->next = temp;
        target->next = temp;
    }
    
    return OK;
}

2.4. 單向循環鏈表的刪除

單向循環列表的插入分爲兩種情況:

  1.  刪除第一個位置

    • 刪除到只剩下首元結點了,則直接將*L鏈表置空

    • 鏈表還有很多數據,但是刪除的是首結點

      • 找到尾結點 target, 使得尾結點 next 指向頭結點的下一個結點,即 target->next = (*L)->next;

      • 新結點做爲頭結點,釋放原來的頭結點

  2.  刪除其他位置

    • 找到刪除結點前一個結點target
    • 定義變量temp,記錄刪除的節點,即:temp = target->next
    • 使得target->next指向刪除位置的下一個結點
    • 釋放需要刪除的結點temp

Status  LinkListDelete(LinkList *L,int place){
    
    LinkList temp,target;
    int i;
    //temp 指向鏈表首元結點
    temp = *L;
    if(temp == NULL) return ERROR;
    
    if (place == 1) {
    
        //1>.如果刪除到只剩下首元結點了,則直接將*L置空;
        if((*L)->next == (*L)){
            (*L) = NULL;
            return OK;
        }
        
        //2>.鏈表還有很多數據,但是刪除的是首結點;
        //1. 找到尾結點, 使得尾結點 next 指向頭結點的下一個結點 target->next = (*L)->next;
        for (target = *L; target->next != *L; target = target->next);
        
        // 記錄頭結點
        temp = *L;
        
        //2. 新結點做爲頭結點,則釋放原來的頭結點
        *L = (*L)->next;
        target->next = *L;
        free(temp);
        
    }else {  //如果刪除其他結點--其他結點

        //1. 找到刪除結點前一個結點target
        for(i=1,target = *L;target->next != *L && i != place -1;target = target->next,i++) ;

        // 記錄要刪除的節點
        temp = target->next;
        // 使得刪除節點的前驅target->next 指向刪除節點的後繼
        target->next = temp->next;
        //3. 釋放需要刪除的結點temp
        free(temp);
    }
    
    return OK;
    
}

2.5. 單向循環鏈表的簡單查詢

int findValue(LinkList L,int value){
    
    int i = 1;
    LinkList p;
    p = L;
    
    //尋找鏈表中的結點 data == value
    while (p->data != value && p->next != L) {
        i++;
        p = p->next;
    }
    //當尾結點指向頭結點就會直接跳出循環,所以要額外增加一次判斷尾結點的data == value;
    if (p->next == L && p->data != value) {
        return  -1;
    }
    return i;
    
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章