Data Structure Final (Fundamental) Exam Review

Data Structure Final (Fundamental) Exam Review

A short time after reading the book Data Structure by Mark Allen Weiss, an inspiring sentence appears:

Some might think Data Structure is difficult, because it’s really important. However, the truth is they are incredibly easy to program and the main obstacle lies in going through adequate practice to write good routines which are generally a few lines.


The course of Reviewing Data Structure can be easily deided into two phases: Routines and Properties of data structures.

Contents

  • Routines needs to know:
    • Binary Search
    • Binary Search Tree
      • Tree Traversal: preorder, postorder, levelorder, inorder
      • Insert
      • Delete
    • Heap
      • Insert
      • Delete
      • Build a Heap
    • DisjointSet
      • Union by size
      • Union by height
      • Union by rank: Find with Path Compression
    • Graph
      • TopSort
      • Unweighted Sort
      • Dijktra
      • Minimal Spanning Tree
      • (*)WeightedNegative
      • AOE and AOV
    • Sort
      • Insertion Sort
      • Shell Sort
      • Heap Sort
      • MergeSort
      • Quick Sort
        • Median 3
    • Hashing
      • Open Addressing
      • Quadratic Probing
      • Find

The routines of Bold items above will be given in this passage.

  • Properties needs to know:
    • In a tree : n 0 = n 2 + 1
    • Draw a Threaded Tree
    • Complete Binary Tree with height h has 2 h + 1 1 nodes
    • Union by rank: T ( M , N ) k 2 M α ( M , N )
    • NetWork Flow Problem
    • Articulation
    • Merge Sort: T ( N ) = 2 T ( N / 2 ) + O ( N ) = N + N l o g N

Codes

Operation on a Tree: here are delete, insert and preorder traversal.

typedef Node* PtrToNode;
typedef Node* PtrToRoot;
struct Node{
    int num;
    PtrToNode Left;
    PtrToNode Right;
};
void preorder(PtrToRoot Root){
    if(Root==NULL) return;
    Print(Root);
    preorder(Root->Left);
    preorder(Root->Right);
    return;
}

Changing the order of visit left son, right son, and itself can give you different traversal order.
Aplication of a Queue can help you achieve lever order traversal, the point is the descendant will only be enqueued after all their parents are dequed.

PtrToNode Insert(PtrToNode node, int num){
    if(node==NULL){
        node = (PtrToNode)malloc(sizeof(struct Node));
        node->num = num;
        node->Left = node->Right = NULL; 
    }
    else if(node->num>num)
        node->Left = Insert(node->Left, num);//trick of recursing
    else if(node->num<num)
        node->Right = Insert(node->Right, num);
    return node; 
}

The problem of Insert lies in you need to trace back to it’s parent when you evetually find where to apply Insertion. Always keeping the parent instead of son and then judge its sons is one easy practice, which is actuallt not bad. Above is the recursive version where return node is the key.

PtrToNode Delete(PtrToNode node, int num){
    if(node==NULL) return;
    if(node->num>num)
        node->Left = Delete(node->Left, num);//key of recursing
    else if(node->num<num)
        node->Right = Delete(node->Right, num);
    else{
        if(node->Left&&node->Right){//two child
            node->num = FindMin(node->Right);
            Delete(node->Right, node->num);
        }
        else{//one child
            if(node->Left) node = node->Left;//using the return at the end
            else return node->Right; 
        }
    }
    return node;
}

This is also a recursive version. The hardest case of having 2 sons can be converted to the general case, node->Right = Delete(node->Right, num) is the point.


Operation on a Heap: here are delete, insert.

typedef Heap* PtrToHeap;//suppose it's a minheap 
struct Heap{
    int size;
    int capacity;
    int* Element;
};
void Insert(PtrToHeap H, int num){
    int i, Tmp;
    for(i = ++H->size; H->Element[i/2]<num; i/=2)
        H->Element[i] = H->Element[i/2];
    H->Element[i] = num;
}

This is a good example of Allen’s words, cannot be fewer and strong. It’s actually procolateup ++H->size is easy to forget. This way of moving items resembles Inserting Sort. but faster based on the tree structure.

void Delete(PtrToHeap H){
    int FirstItem = H->Element[0];
    int LastItem = H->Element[H->size--];
    int i, child;
    for(i=0; 2*i<=H->size; i = child){
        child = i*2;
        if(child != H->size&&H->Element[child+1]<H->Element[child])
            child++;
        if(H->Element[child]<LastItem)
            H->Element[i] = H->Element[child];
        else break;
    }
    H->Element[i] = LastItem;
    return FirstItem;
}

It’s procolate down, where the judgement of child is important.


Operation on a DisjSet: here is path compression.

int* DisjSet;
int Find(int* DisjSet, int num){//recursive version
    if(DisjSet[num]>0)
        return DisjSet[num] = Find(DisjSet[num]);
    return num;
}

Recursive version, where the return is the key. because it gaurantees all the nodes on the way will finally points to the root.

int Find(int* DisjSet, int num){//iterative version
    int root = num;
    while(DisjSet[root]>0) root = DisjSet[root];
    int trail, lead;
    for(trail = num; trail!=root; trail = lead){
        lead = DisjSet[trail];
        DisjSet[trail] = root;
    }
    return root;
}

Iterative Version. Using trail and lead to move, like the i and child in the heap.


Operation on a Graph: here is TopSort.

typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
    int AdjV;
    PtrToAdjVNode Next;
};

typedef struct Vnode{
    PtrToNode FirstEdge;
}AdjList[MaxVertexNum];

typedef struct GNode *PtrToGNode;
struct GNode{
    int Nv;
    int Ne;
    AdjList G; 
};

typedef PtrToGNode LGraph;

This is the adjacent list version of representing a graph. The adjacent metrix is theoratically good for dense situation.

int TopSort(LGraph Graph){
    int indegree[GNode->Nv];
    int i;
    for(i=0; i<Graph->Nv; i++)
        indegree[i] = 0;

    PtrToAdjVNode j;
    for(i=0; i<Graph->Nv; i++){
        for(j=Graph->G[i].FirstEdge; j!=NULL; j = j->Next)
            indegree[j->AdjV]++;
    }
    int TmpPos;
    Queue Q = QueueInitialize();
    for(i=0; i<Graph->Nv; i++)
        if(indegree[i]==0)
            Inqueue(Q, i);

    int Count = 1;
    while(!IsEmpty(Q)){
        TmpPos = Dequeue(Q);
        j = Graph->G[TmpPos]->FirstEdge;
        while(j!=NULL){
            if(--indegree[j->AdjV]==0){
                Enqueue(Q, j->AdjV);
                Count++; 
            }
            j = j->Next; 
        }
    }

    if(Count==Graph->Nv)
        return 1;
    else return -1;
}

In the routine of Top Sort, the indegree list is the point. If lists of Distance[] and Know[] is added, harder situation can be applied.


Operation to Sort: here is Inserting Sort, Shell Sort and Quick Sort.

void InsertionSort(int *A, int size){//Quadratic Algorithm
    int P, Tmp, i;
    for(P=1; P<size; P++){
        Tmp = A[P];
        for(i=P; i>0&&A[i-1]>Tmp; i--)
            A[i]=A[i-1];
        A[i] = Tmp;
    }
} 

Keep in mind that P is the coming card and sorting start from the second card (number 1). Then, when you get a new card, the cards before P is sorted.

void ShellSort(int *A, int size){
    int increment;
    int P, Tmp, i;
    for(increment=size/2; increament>0; increament/=2){
        for(P=increment; P<size; P++){
            Tmp = A[P];
            for(i=P; i>increment&&A[i-increment]>Tmp; i-=increment)
                A[i]=A[i-increment];
            A[i] = Tmp;
        }
    }
}

Application of increment is the point. It’s important to know it start from size/2 and /=2 each time (in the routine not in practice). It’s also easy to forget P needs to ++ instead of += incement.

int Median3(int *A, int front, int last){
    if(A[last/2]<A[front])
        swap(A, front, last/2);
    if(A[last/2]>A[last])
        swap(A, last/2, last);
    if(A[last/2]<A[front])
        swap(A, front, last);
    swap(A, last/2, last-1);
    return A[last-1];
}

The median item is hided just before the last item.

void Qsort(int*A, int front, int last){
    if(last-front<20){
        InsertionSort(A+front, last-fornt+1);
        return;
    }
    else{
        int median = median3(A, front, last);
        int i = front;
        int j = last-1;
        for(;;){
            while(A[++i]<median){}
            while(A[--j]>median){}
            if(i<j){
                swap(A, i, j);
            }
            else break;
        } 
        swap(A, i, last-1);
        Qsort(A, front, i-1);
        Qsort(A, i+1 ,last);
    }
    return;
}

int QuickSort(int*A, int size){
    Qsort(A, 0, size-1);
}

One big for loop and two while loop constitude Quick Sort. i=front and j=last -1, A[++i] A[–j] is always easy to make mistake.


Operation to Hash: here is Inserting based on quadratic probing.

typedef struct HashEntry Cell;
typedef struct HashTbl* HashTable;
struct HashEntry{
    int Element;
    int State;//0 represent not occupied    
};

struct HashTbl{
    int size;
    Cell *TheCells;
};
int Find(HashTable H, int key){
    int Postiion = key%H->size;
    int CurrentPosition = Posiition;
    int CollisionNum = 0;
    while(H->TheCells[CurrentPosition].State==1&&H->TheCells[CurrentPosition].Element!=key){
        if(CollisionNum>H->size)return-1;
        CollisionNum++;
        CurrentPosition = (Position + CurrentPosition*CurrentPosition)%H->size;
    } 
    return CurrentPosition;
}

CurrentPosition is important to keep. Two check in the while loop is also important.

void InSert(HashTable H, int key){
    int Position = Find(H, key);
    if(Position>=0){
        H->TheCells[Position].Element = key;
        H->TheCells[Position].State = 1;
        return 0;
    }
    return -1;
}

The sentence by Allen Weiss is translated back to English because the English version book is not at hand.


Never mind happiness, do your duty.

猜你喜欢

转载自blog.csdn.net/yfren1123/article/details/79157721