0Pricing
C Academy · บทเรียน

แทรก ค้นหา และลบ

การดำเนินการหลัก

แทรก ค้นหา และลบ เป็นบทเรียน C Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน

การดำเนินการหลักสามอย่าง

ตารางแฮชทุกตารางรองรับการดำเนินการสามอย่าง ได้แก่ การแทรก การค้นหา และการลบ เมื่อใช้แฮชที่ดีและมีอัตราการใช้งานเหมาะสม ทั้งสามการดำเนินการจะใช้เวลาเฉลี่ย O(1)

เราจะสร้างตารางที่ใช้การเชื่อมโยงทีละขั้นตอน

ชนิดของตารางและโหนด

เราจะกำหนดโหนดที่เก็บสตริงคีย์ที่คัดลอกไว้และค่าจำนวนเต็ม รวมถึงโครงสร้างตารางที่เก็บอาร์เรย์ช่องและความจุของตาราง

#include <stdio.h>

typedef struct Node {
    char *key;
    int value;
    struct Node *next;
} Node;

typedef struct {
    Node **buckets;
    unsigned capacity;
    unsigned size;
} HashTable;

int main(void) {
    printf("types defined\n");
    return 0;
}

การสร้างตาราง

จัดสรรตารางและอาร์เรย์ช่องที่ตั้งค่าเป็นศูนย์ด้วย calloc เพื่อให้ทุกช่องเริ่มต้นเป็น NULL

#include <stdio.h>
#include <stdlib.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;
typedef struct { Node **buckets; unsigned capacity, size; } HashTable;

HashTable *ht_create(unsigned cap) {
    HashTable *t = malloc(sizeof *t);
    t->buckets = calloc(cap, sizeof(Node *));
    t->capacity = cap; t->size = 0;
    return t;
}

int main(void) {
    HashTable *t = ht_create(16);
    printf("capacity=%u size=%u\n", t->capacity, t->size);
    return 0;
}

ตัวช่วยแฮช

เรานำ DJB2 กลับมาใช้และลดค่าลงเป็นดัชนีช่อง ตัวช่วยนี้ถูกใช้โดยการดำเนินการทั้งสามอย่าง

#include <stdio.h>

unsigned long djb2(const char *s) {
    unsigned long h = 5381; int c;
    while ((c = (unsigned char)*s++)) h = ((h << 5) + h) + c;
    return h;
}

unsigned bucket_of(const char *key, unsigned cap) {
    return (unsigned)(djb2(key) % cap);
}

int main(void) {
    printf("%u\n", bucket_of("name", 16));
    return 0;
}

การแทรก: อัปเดตหรือนำไปไว้ด้านหน้า

เมื่อแทรก ให้ค้นหาช่องก่อน หากมีคีย์อยู่แล้ว ให้อัปเดตค่า มิฉะนั้นให้จัดสรรโหนดใหม่ (พร้อมคีย์ที่คัดลอกผ่าน strdup) แล้วนำไปไว้ด้านหน้า

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;

Node *insert(Node *head, const char *key, int val) {
    for (Node *p = head; p; p = p->next)
        if (strcmp(p->key, key) == 0) { p->value = val; return head; }
    Node *n = malloc(sizeof *n);
    n->key = strdup(key); n->value = val; n->next = head;
    return n;
}

int main(void) {
    Node *b = NULL;
    b = insert(b, "a", 1);
    b = insert(b, "a", 99); /* update */
    printf("%s=%d\n", b->key, b->value);
    return 0;
}

การค้นหา

การค้นหาจะแฮชคีย์ จากนั้นเดินผ่านรายการในช่องโดยเปรียบเทียบคีย์ด้วย strcmp และคืนค่าตัวชี้ไปยังค่า (หรือคืนค่า NULL หากไม่พบ)

#include <stdio.h>
#include <string.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;

int *lookup(Node *head, const char *key) {
    for (Node *p = head; p; p = p->next)
        if (strcmp(p->key, key) == 0) return &p->value;
    return NULL;
}

int main(void) {
    Node n2 = {"y", 20, NULL};
    Node n1 = {"x", 10, &n2};
    int *v = lookup(&n1, "y");
    printf("%d\n", v ? *v : -1);
    return 0;
}

การลบ: เชื่อมรายการใหม่

การลบจะเดินผ่านช่องโดยเก็บตัวชี้ไปยังโหนดก่อนหน้า จากนั้นเชื่อมรายการข้ามโหนดเป้าหมายใหม่และคืนหน่วยความจำของโหนดนั้น (ทั้งคีย์ที่คัดลอกและโหนด)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;

Node *delete_key(Node *head, const char *key) {
    Node *prev = NULL, *cur = head;
    while (cur) {
        if (strcmp(cur->key, key) == 0) {
            if (prev) prev->next = cur->next; else head = cur->next;
            free(cur->key); free(cur);
            return head;
        }
        prev = cur; cur = cur->next;
    }
    return head;
}

int main(void) {
    Node *b = malloc(sizeof *b);
    b->key = strdup("a"); b->value = 1; b->next = NULL;
    b = delete_key(b, "a");
    printf("%s\n", b ? "left" : "empty");
    return 0;
}

นำทุกส่วนมาประกอบกัน

ตารางแบบสมบูรณ์จะรวมสิ่งเหล่านี้เข้าด้วยกัน โดยคำนวณช่องก่อนแล้วมอบหมายงานให้ตัวช่วยจัดการรายการ ในที่นี้คือตารางขนาดเล็กที่ทำงานได้อย่างครบถ้วน

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;

unsigned long djb2(const char *s){unsigned long h=5381;int c;while((c=(unsigned char)*s++))h=((h<<5)+h)+c;return h;}

#define CAP 16
Node *table[CAP];

void put(const char *k, int v) {
    unsigned i = djb2(k) % CAP;
    Node *n = malloc(sizeof *n);
    n->key = strdup(k); n->value = v; n->next = table[i];
    table[i] = n;
}
int get(const char *k) {
    for (Node *p = table[djb2(k) % CAP]; p; p = p->next)
        if (!strcmp(p->key, k)) return p->value;
    return -1;
}

int main(void) {
    put("age", 30); put("score", 95);
    printf("age=%d score=%d\n", get("age"), get("score"));
    return 0;
}

เหตุผลที่ต้องคัดลอกคีย์

เราเก็บคีย์ด้วย strdup เพื่อให้ตารางมีสำเนาของตนเอง หากเราเก็บตัวชี้ของผู้เรียก คีย์อาจถูกเปลี่ยนแปลงหรือลบหน่วยความจำไปโดยที่เราไม่ทราบ ทำให้การค้นหาเสียหาย

นั่นหมายความว่าการลบต้องใช้ free กับคีย์ที่คัดลอกไว้ด้วย

ความซับซ้อนด้านเวลา

เมื่อใช้แฮชแบบสม่ำเสมอและควบคุมอัตราการใช้งานให้อยู่ใกล้ 0.75:

  • การแทรก: O(1) โดยเฉลี่ย
  • การค้นหา: O(1) โดยเฉลี่ย
  • การลบ: O(1) โดยเฉลี่ย

กรณีเลวร้ายที่สุดคือ O(n) เมื่อคีย์ทั้งหมดชนกันในช่องเดียว

การคืนหน่วยความจำของทั้งตาราง

เพื่อป้องกันหน่วยความจำรั่ว ให้คืนหน่วยความจำของทุกโหนดในทุกช่อง จากนั้นคืนหน่วยความจำของอาร์เรย์ช่อง และสุดท้ายคืนหน่วยความจำของโครงสร้างตาราง

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Node { char *key; int value; struct Node *next; } Node;

void free_bucket(Node *head) {
    while (head) { Node *nx = head->next; free(head->key); free(head); head = nx; }
}

int main(void) {
    Node *b = malloc(sizeof *b);
    b->key = strdup("k"); b->value = 1; b->next = NULL;
    free_bucket(b);
    printf("freed\n");
    return 0;
}

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจของคุณเกี่ยวกับการดำเนินการหลัก

สรุปทบทวน

คุณได้ใช้งานการดำเนินการหลักสามอย่างของตารางแฮชด้วยการเชื่อมโยง

  • การแทรกจะอัปเดตหรือนำโหนดไปไว้ด้านหน้า
  • การค้นหาจะเดินผ่านรายการในช่องด้วย strcmp
  • การลบจะเชื่อมรายการใหม่และคืนหน่วยความจำทั้งคีย์และโหนด
  • จัดการคีย์ของคุณเองด้วย strdup และคืนหน่วยความจำทุกส่วนเมื่อเลิกใช้งาน

คำถามที่พบบ่อย

บทเรียน “แทรก ค้นหา และลบ” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “แทรก ค้นหา และลบ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แทรก ค้นหา และลบ”

การดำเนินการหลัก คุณปฏิบัติ C Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “แทรก ค้นหา และลบ” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C Academy นี้ได้ไหม

ได้ บทเรียน C Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ฟังก์ชันแฮช
  2. การจัดการการชนกัน
  3. แทรก ค้นหา และลบ
  4. การปรับขนาดและปัจจัยโหลด
← กลับไปที่ C Academy