dung18j / c-notes

C Programming Language Summary
0 stars 9 forks source link

C Programming Language Course

Variables

Array

Decision

Looping

Function

Macro

Bit Operation

Giới thiệu

Bit A Bit B A AND B A OR B A XOR B NOT A
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0

Trong ngôn ngữ C, có 6 toán tử được gọi là toán tử bitwise (hay còn được gọi là toán tử bit vì chúng hoạt động ở mức độ bit).

Một số lưu ý khi sử dụng Bitwise Operations

1. Toán tử dịch trái và dịch phải không nên được sử dụng cho các số âm.

2. Phép OR bit của hai số chỉ là tổng của hai số đó nếu không có nhớ, khi có nhớ kết quả sẽ cộng thêm phép AND bit của chúng.

3. Toán tử XOR bit là toán tử phổ biến nhất

4. Các toán tử Bitwise không nên được sử dụng thay thế cho các toán tử logic.

5. Toán tử dịch trái (left-shift) và dịch phải (right-shift) tương đương với phép nhân và chia cho 2 tương ứng.

6. Toán tử & (AND) có thể được sử dụng để kiểm tra nhanh xem một số có phải là số lẻ hay số chẵn.

7. Toán tử ~ (NOT) cần được sử dụng cẩn thận.

Set, Clear, Toggle, Read bit

  #include <stdio.h>

  unsigned int setBit (unsigned int value, unsigned int k); 
  unsigned int clearBit (unsigned int value, unsigned int k);
  unsigned int readBit (unsigned int value, unsigned int k); 
  unsigned int toggleBit (unsigned int value, unsigned int k);
  void printBinary (unsigned int num);

  int main(){
      unsigned int value = 8; unsigned int k = 2;
      unsigned int SetBit, ClearBit, ReadBit, ToggleBit;
      SetBit = setBit(value, k - 1);
      ClearBit = clearBit(value, k - 1);
      ReadBit = readBit(value, k - 1);
      ToggleBit = toggleBit(value, k - 1); 
      printf("Value: %u, Order: %u\n", value, k);
      printf("Value in binary:"); printBinary(value);
      printf("SetBit: \t"); printBinary(SetBit);
      printf("ClearBit: \t"); printBinary(ClearBit);
      printf("ReadBit: \t"); printBinary (ReadBit); 
      printf("ToggleBit: \t"); printBinary(ToggleBit);
      return 0;
  }

  unsigned int setBit (unsigned int value, unsigned int k)
  {
      return value |= (1<<k);
  }

  unsigned int clearBit (unsigned int value, unsigned int k){
      return value &= ~(1<<k);
  }

  unsigned int readBit(unsigned int value, unsigned int k){ 
      return (value & (1<<k)) >> k;
  }

  unsigned int toggleBit (unsigned int value, unsigned int k){ 
      return value ^= (1<<k);
  };

  void printBinary(unsigned int num) {
      int i;
      int num_bits = (num == 0) ? 1 : log2(num) + 1;
      for (i = num_bits - 1; i >= 0; i--) {
          printf("%u", readBit(num, i));
      }
      printf("\n");
  }

Bit mask

Bit Field in C

  struct
  {
  data_type member_name : width_of_bit-field;
  };

data_type: Đây là một kiểu số nguyên xác định giá trị của trường bit mà sẽ được hiểu. Kiểu có thể là int, signed int hoặc unsigned int. member_name: Tên của bit field. width_of_bit-field: Số bit trong trường bit. Độ rộng phải nhỏ hơn hoặc bằng với độ rộng bit của kiểu dữ liệu được chỉ định.

  #include <stdio.h>
  union test {
      // Bit-field member x with 3 bits
      unsigned int x : 3;
     // Bit-field member y with 3 bits
     unsigned int y : 3;
     // Regular integer member z
     int z;
   };
   int main()
   {
     // Declare a variable t of type union test
     union test t;
     // Assign the value 5 to x (3 bits)
     t.x = 5;
     // Assign the value 4 to y (3 bits)
     t.y = 4;
     // Assign the value 1 to z (32 bits)
     t.z = 1;
     // Print the values of x, y, and z
     printf("t.x = %d, t.y = %d, t.z = %d", t.x, t.y, t.z);
     return 0;
   }
}

Memory Management

Pointer Basic

Pointer Advance

Data Structure

Algorithms

Thuật toán được chia ra nhiều loại khác nhau, tuy nhiên, note này chỉ nói đến thuật toán cơ bản là tìm kiếm và sắp xếp trên mảng hoặc trên danh sách liên kết.

Searching algorithm

Mục đích của thuật toán tìm kiếm là tìm ra dữ liệu tương ứng với khóa $k$ trong danh sách. Dữ liệu tìm kiếm có thể là chỉ số của phần tử trong mảng hoặc dữ liệu được người dùng định nghĩa. Ví dụ:

struct data {
    int key; // khóa tìm kiếm
    int data; // dữ liệu
};

Mỗi thuật toán lại có độ phức tạp tính toán khác nhau. Cần để ý đến:

Linear Search

Tìm kiếm tuần tự là phương pháp tìm kiếm phần tử trong một mảng hay một danh sách liên kết bằng cách so sánh lần lượt từng phần tử trong danh sách với khóa k.

Ví dụ tìm kiếm vị trí phần tử $k$ trong mảng:

int find(int *arr, int len, int k) {
    for (int i = 0; i < len; i++) {
        if (arr[i] == k) {
            return i;
        }
    }
    return -1;
}

Ví dụ tìm kiếm phần tử $k$ trong danh sách liên kết:

struct node {
    int key;
    int data;
    struct node* next;
};

struct node* find(struct node* node, int k) {
    struct node *inode;
    for (inode = node; inode->next != NULL; inode = inode->next) {
        if (inode->key == k) {
            return inode;
        }
    }
    return NULL;
}

Có thể thấy, với mỗi phần tử trong danh sách, có 2 thao tác so sánh được thực hiện: Kiểm tra điều kiện dừng khi hết mảng và kiểm tra phần tử hiện tại thỏa mãn hay chưa. Có thể tối ưu thao tác này bằng cách thay thế phần tử tìm kiếm cuối cùng bằng phần tử có khóa hợp lệ (phần tử chặn). Như vậy, chắc chắn sẽ tìm thấy phần tử trong mảng và không cần kiểm tra điều kiện hết mảng. Ví dụ minh họa:

int find(int *arr, int len, int k) {
    int last = arr[len-1];
    arr[len-1] = k; // đặt phần tử cuối cùng = k

    int i;
    while (arr[i] != k) {
        i+=1;
    }

    arr[len-1] = last; // trả lại giá trị phần tử cuối

    if (i == len - 1) {
        if (last == k) {
            return len - 1;
        } else {
            return -1;
        }
    } else {
        return i;
    }
}

Độ phức tạp thời gian:

Binary Search

Đối với danh sách kiểu mảng (có thể truy cập phần tử bằng chỉ số) đã được sắp xếp, việc tìm kiếm có thể được cải thiện đáng kể. Khi so sánh khóa $k$ với phần tử giữa danh sách, dựa vào kết quả so sánh mà một nửa danh sách có thể được loại bỏ do biết chắc chắn là các phần tử trong đó nhỏ hơn hoặc lớn hơn khóa tìm kiếm $k$.

Ví dụ tìm kiếm với $k = 8$ trong dãy $[1, 2, 3, 4, 5, 6, 7, 8, 9]$ có phần tử chính giữa bằng $5$. Với $k=8>5$, ta sẽ chỉ cần tìm kiếm $k$ trong dãy $[6, 7, 8, 9]$ với phương pháp tương tự.

// tìm kiếm trong danh sách tăng dần
int binary_search(int *arr, int first, int last, int k) {
    if (last >= first) {
        int mid = (first+last)/2; // first + (last-first)/2
        if (arr[mid] == k) {
            return mid;
        } else if (arr[mid] > k) {
            // phần tử hiện tại lớn hơn khóa
            // -> tìm kiếm ở nửa trước
            binary_search(arr, first, mid-1, k);
        } else {
            // phần tử hiện tại nhỏ hơn khóa
            // -> tìm kiếm ở nửa sau
            binary_search(arr, mid+1, last, k);

        }

    } else {
        // last < first: danh sách rỗng - không tìm thấy
        return -1;
    }
}

Độ phức tạp thời gian:

Sorting algorithm

Insertion Sort

void insertion_sort(int *arr, int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        for (j = i - 1; j>=0; j--) {
            if (arr[j] > key) {
                arr[j+1] = arr[j];
            } else {
                break;
            }
        }
        arr[j+1] = key;
    }
}

Bubble Sort

void bubble_sort(int*arr, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if(arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

Selection Sort

void selection_sort(int *arr, int n) {
    for (int i = 0; i < n-1; i++) {
        int index_min = i;
        int min = arr[i];

        // tìm chỉ số và giá trị phần tử nhỏ hơn arr[i]
        for (int j = i+1; j < n; j++) {
            if (arr[j] < min) {
                index_min = j;
                min = arr[j];
            }
        }
        arr[index_min] = arr[i];
        arr[i] = min;
    }
}

Quick Sort


void swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

// Partition the array using the last element as the pivot
int partition_sort(int arr[], int low, int high)
{
    // Choosing the pivot
    int pivot = arr[high];

    // Index of smaller element and indicates
    // the right position of pivot found so far
    int i = (low - 1);

    for (int j = low; j <= high - 1; j++) {

        // If current element is smaller than the pivot
        if (arr[j] < pivot) {

            // Increment index of smaller element
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

// The main function that implements QuickSort
// arr[] --> Array to be sorted,
// low --> Starting index,
// high --> Ending index
void quickSort(int arr[], int low, int high)
{
    if (low < high) {

        // pi is partitioning index, arr[p]
        // is now at right place
        int pi = partition_sort(arr, low, high);

        // Separately sort elements before
        // partition and after partition
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

Optimization

Common Defects

Unit Testing

Git Version Control