DataStructure

1. Class in C++
1.1 Data Structure
Data Structure refers to a collection of data elements with interrelationships and structures, including data logical structure,data storage structure,and data operation.
1.2 Class in C++
- A class consists of variables, called fields,together with Functions,called method, that act on fields.
- Method Interface: the explicit information a user will need in order to invoke the methods.
- Object: a variable whose type is a class, an instance of its class.
- User’s view of a class: method interface(= precondition + postcondition | method heading)
- Developer’s view of a class: fields and method definitions
- Inheritance: the ability to define a new class(subclass) that includes all the fields and some or all of the methods of an existing class(superclass). The subclass may declare new fields and methods, and may override existing methods.
1.3 Principle of data abstraction
A user’s code should not access the implementation details of the class used.
1.4 The open-closed principle
Every class should be
- Open: Extendible through inheritance
- Closed: Stable for existing applications
1.5 Subclass substitution rule
Whenever a superclass object is called for in an expression, a subclass object may be substituted.
2. Container Classes
2.1 Dynamic variable
- A dynamic variable is one that is created and destroyed by the programmer during execution.(this can save a lot of space in memory).
- Dynamic variables are stored in the heap,a large area of memory
- Dynamic variables are never accessed directly. They are accessed through pointer variables.
- A pointer variables contains the address of a variable
- A pointer type is a type followed by an asterisk:
String* sPtr;
- To create a dynamic variables, the new operator is used:
sPtr = new string;
- To access the dynamic variable, we dereference the pointer variables by the dereference operator,’*****’,is place in front of the pointer variables:
*sPtr
- The dereference-and-select operator is
->
,sPtr->length()<=6
is equivalent to(*sPtr).length < = 6
- The destroy a dynamic variables the delete operator is used:
Delete sPtr;
2.2 Arrays
string* words;
Here, words can be a pointer to either a single string or to an array of strings.- Pointer to an array of strings:
words = new string[7];
- The index operator [], is used to reference the individual items:
words[1]
is equivalent to*(words+1)
(Universal array-pointer law) - Random-access: any item in an array can be accessed or modified immediately given the index of the item
- To deallocate the space for an array, use an empty index:
delete[] words
2.3 Container Classes
Container : is a variable that consists of a collection of items.
Container class: is a class whose objects are containers
Two widely used structure for storing items in a container object:
Array structure: one of field is the class is an array, the items are stored in this array
- Advantage: allows random-access of items
- Drawback: large-scale movement of items for inserting,deleting, and resizing
Linked structure: each item has an associated link - a pointer to the next item in the container
Define a template,or mold, allow the definition, at compile-time, of container class of some fixed type, let user select the item type when they defines the container object.
The container class is a template class, and the given type is a template argument
The definition of a template class starts with the template parameter,the type that will be replaced by the template argument when the container object is defined.
The Linked class has an embedded iterator class. An Iterator is an object that enables a user to loop through a container without violating the principle of data abstraction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30template <typename T>
class Linked{
private:
struct Node{
T item;
Node* next;
}
Node* head;
length;
public:
class Iterator{
friend class Linked<T>
private:
Node* nodePtr;
protected:
Iterator(Node* newPtr) // is protected becasue users know nothing node.
public:
bool operator== const;
bool operator!= const;
T& operator*() const;
bool operator++(int); // post-increment operator
}
Linked();
long size();
void push_front(const T& newItem);
void pop_front();
bool empty();
Iterator begin();
Iterator end();
}
Destructors
- A destructor is a method that deallocates the space occupied by an object.
- No return type, no parameters !
- The destructor for an object is automatically called when the object goes out-of-scope, that is, when the object is no longer accessible.
- If a class does not explicitly define a destructor, the compiler will automatically provide one. this default destructor simply calls the destructor for each object field.
- We had to explicitly define a destructor for the class without object fields to avoid the possibility of a massive memory leak.
2.4 Generic Algorithms
A iterator in a class that supports those operators(==,!=,++,*) is called an input iterator.
Template functions, also called generic algorithms, constitute one of the three major components of the Standard Template Library.
The other two essentials are Container classes and Iterators
These connection: Generic algorithms operate on containers through iterators.
3. Time Complexity
3.1 Software Engineering
- MOORE’S Law: computer power(circuits per chip) doubles about every 18 months.
- Software engineering: A systematic approach to the development of medium-to-large programs
- Software-development-life cycle
- Stage I : Problem analysis
- Stage II: Program Design
- Stage II: Program Implementation
- Stage IV: Program
- Modification due to
- errors found
- enhancements made
- change in computing environment
3.2 Execution-time Complexity
- Execution-time requirements: number of statements executed in a trace of the method,given a function of n, the problem size.
- worstTime(n): the maximum number of statements executed in a trace.
- averageTime(n): the average number of statements executed in a trace.
- Maximum and average are over all possible traces of the method,for all possible field, parameter, and input values
3.3 Big-O
Definition of big-O
Let g be a function that has non-negative integer arguments and returns a non-negative value for all arguments.
Define o(g),the order of g, to be the set of functions f such that for some pair of non-negative constants c and k,
$$
f(n)<=c \ g(n) \ \ \ for \ all \ \ n >=k
$$Say f is O(g) ,”f id O of g”,f is eventually less than or equal to some constant time g. So g can be viewed as an upper bound for f.
极限定义
- 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用f(n)表示,若有某个辅助函数g(n),使得当n趋近与无穷大时,g(n)/f(n)的极限值为不等于0的常数,则称g(n)是f(n)的同量级函数。
- 记作f(n)=O(g(n)),称O(g(n))为算法的渐进时间复杂度,简称为时间复杂度
Smallest upper bound : o(g) is the smallest upper bound of f that f is o(g) and for any function h, if f is o(h), then
$$
o(g) \subset o(h)
$$Growth Rates
Examples
1
2
3
4
5for(int i=8;i<n-2;i++){
cout << j*j << endl;
if(n/2 > j)
cout << j*n << endl;
} // O(n)1
2
3while(n>1){
n = n/2;
} // O(log n)1
2
3
4
5
6for (int j=0;j<n;j++){}
cout << (j*j) << endl;
}
while(n>1){}
n/=2;
}// O(n)1
2
3
4
5
6for(int j=0; j < n; j++){
int temp = n;
while(temp > 1){
temp = temp/2;
}
} // O(nlogn)1
2
3for(int i=0; i*i < n; i++){
cout << i << endl;
} // O(n^1/2)worstTime(n) Smallest upper bound Constant O(1) logarithmic in n O(log n) linear in n O(n) quadratic in n O(n^2) Method - estimate conventions(公约)
- n = number of items in the container
- If no estimate of worstTime(n) given , worstTime(n) is constant
- If no estimate of averageTime(n) given, o(averageTime(n)) = o(worstTime(n))
3.3 Run Time Analysis
time(null)
: return the number of seconds elapsed since midnight on january 1,1970The skeleton of a timing program
1
2
3
4
5
6
7
8
9long start_time,
finish_time,
elapsed_time;
start_time = time(NULL);
/*
Perfrom Task
*/
elapsed_time = finish_time - start_time; // Calculate elapsed time for the task
cout << elapsed_time << endl;
3.4 Randomness
Random number: A number is selected randomly from a collection of numbers if each number has an equal chance of being selected.
rand()
: defined in stdlib.h , returns a “random” intseed
: an unsigned int used in the calculation of the value returned by rand()1
2
3
4srand(100);
for(int i=0;i<10;i++){
cout << rand()%4 << " ";
} // Random number
4. Recursion
A function is recursive if it include a call to itself
基本形式:
1 | if simplest case |
Consider recursion when :
- simplest case(s) can be solved directly
- complex cases can be solved in terms of simpler cases of the same form.
Any problem that can be solved recursively can also be solved iteratively
An Iterative functions is one that has a loop statement
4.1 Factorials
We can calculate
n!
with(n-1)!
,because
$$
n! = n*(n-1)!
$$1
2
3
4
5
6
7
8
9// Precondition: n>0
// Postcondition: The values returned is n!
long factorial(int n){
if(n==0||n==1){
return 1;
}else{
return n*factorial(n-1);
}
} //factorialExecution frames
Allow to see what happens during the execution of a recursive function
is a box with information (such as parameters values) related to one call to the function
Worsttime(n): O(n) – is linear in n
- the number of loop iterations as a function of n
- the number of recursive calls as a function of n
Worstspace(n): O(n) – is linear in n
- the number of variables needed in any trace of this function is linear in n.
Iterative function
1
2
3
4
5
6
7
8
9long factorial(int n){
int product = n;
if(n==0)
return 1
for(int i=n-1;i>1;i--){
product = product*i;
}
return product;
} // function factorial- worsttime : O(n)
- worstspace: O(1)
4.2 Converting from Decimal to Binary
We need to print the binary equivalent of
n/2
before we printn%2
, stop whenn=1 or n=0
,and outputn
1
2
3
4
5
6
7
8
9
10// Precondition: n>=0
// Postcondition: The binary equivalent of n has been printed
void writeBinary(int n){
if(n==0||n==1){
cout << n;
}else{
writeBinary(n/2);
cout << n%2;
}
} // writeBinaryworsttime(n): O(log n)
worstspace(n): O(log n)
4.3 Towers of Hanoi
Given 3 poles (a,b,c) and n disks of increasing size (1,2,3,…,n), move the n disks form pole a to pole b, use pole c for temporary storage.
- only one disk may be moved at any time
- no disk may ever be placed on top of a smaller disk
- the top disk on any pole may be moved to either of the other poles
We will be able to move 4 disks from one pole to another if we are able to figure out how to move 3 disks form one pole to another
if n==1, move disk 1 from pole 'a' to pole 'b' otherwise, 1. move n-1 disks from 'a' to 'c', with 'b' as a temporary 2. move disk n from 'a' to 'b' 3. move n-1 disks from 'c' to 'b', with 'a' as a temporary
1
2
3
4
5
6
7
8
9
10
11
12
13
- ```c++
// Percondition: n>0
// Postcondition: The steps needed to move n disks from pole orig to pole dest have been written out. Pole temp is used for temporary storage
void move(int n, char orig,char dest,char temp){
if(n==1){
cout << "Move disk 1 from " << orig << "to " << dest << endl;
}else{
move(n-1,orig,temp,dest);
cout << "Move disk " << n << "from " << orig << "to " << dest << endl;
move(n-1, temp , dest, orig);
}
} // moveworsttime
$$
worsttime(n) \approx O(2^{n})\
because\ 2^{n}-1 \ disks must be moved
$$
4.4 Backtracking
Backtracking is the strategy of trying to reach a goal by a sequence of chosen positions, with re-tracing in reverse order of position that cannot lead to the goal.
General-Purpose Backtrack Class – Application.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Application{
friend ostream& operator <<(ostream& stream, Application& app);
public:
Position generateInitialState();
bool valid(const Positon& pos);
void record(const Positon& pos);
bool done(const Position& pos);
void undo(const Position& pos);
class Iterator{
public:
Iterator(const Position& pos);
Position operator++(int);
bool atEnd();
protected:
void* fieldPtr;
} // class Iterator
} // class ApplicationBackTrack class
1
2
3
4
5
6
7class BackTrack{
protected:
Application app;
public:
BackTrack(const Application& app);
bool tryToSolve(Position pos);
} // class BackTracThe tryToSolve method first constructs an iterator from pos and then loops until success has been achieved or no more iterations are possible.
Ecah loop iteration considers three possibilities for the new position generated by the iterator
- goal reached, return true
- valid position but not the goal; then call tryToSolve to see if success is possible from the current value of pos.
- invalid position and iterator at end, return false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class BackTrack::tryToSolve(Position pos){
bool success = false;
Application::Iterator itr(pos);
while(!success && !itr.atEnd()){
pos = itr++;
if(app.valid(pos)){
app.record(pos);
if(app.done(pos)){
return true;
}else{
sucess = tryToSolve(pos);
if(!success)
app.undo(pos);
} // not done
} // a valid position
} // while
return success
}// method tryToSolvemain function
1
2
3
4
5
6
7
8
9
10
11
12
13
14Application app;
Backtrack b(app);
Position start = app.generateInitialState();
if(!app.valid(start)){
cout << FAILURE << endl;
}else{
app.record(start);
if(app.done(start) || b.tryToSolve(start)){
cout << SUCCESS << endl << app;
}else{
app.undo(start);
cout << FAILURE << endl;
}// failure
}// start is validMaze Problme
Position calss
1
2
3
4
5
6
7
8
9
10
11class Position{
public:
position();
Positon(int row, int column);
void setPosition(int row, int column);
int getRow();
int getColumn();
protected:
int row,
column;
}// class Positioin1
2
3struct itrFields{
int row, column, direction;
}Iterator constructor
1
2
3
4
5
6
7Application::Iterator::Iterator(Position pos){
iterFields* itrPtr = new itrFields;
itrPtr->row = pos.getRow();
itrPtr->column = pos.getColumn();
itrPtr->direction = 0;
fieldPtr = itrPtr;
} //constructoroperator++(int)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Position Application::Iterator::operator++(int){
itrFields* itrPtr = (itrFields*)filedPtr;
int nextRow = itrPtr->row, nextColumn = itrPtr->columnl
switch(itrPtr->direction++){
case 0:
nextRow = itrRow->row-1; // north
break;
case 1:
nextColumn - itrPtr->Column+1; // east
break;
case 2:
nextRow = itrPtr->row+1; // south
break;
case 3:
nextColumn = itrPtr->column-1; // west
} // switch
Positioin next(nextRow, nextColumn);
return next;
}// operator++(int)
4.5 Searching an Array
Generic algorithm find – worsttime: O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last , const T& value);{
while(first != last && *first != value){
++ first;
return first;
}
}
int main(){
// Find by pointer
string words[20];
string* wordPtr = find(words ,words+20, myWord);
cout << myWord;
if(wordPtr!=words+20){
cout << "at index" << (wordPtr - words) << endl;
}else{
cout << "Not Found." << endl;
}
// Find by Linked::Iterator
Linked<int> myLinked;
Linked<int>::Iterator it;
itr = find(myLinked.begin(),myLinked.end(),someInt);
if(itr != myLinked.end()){
cout << "Found" << endl;
}else{
cout << "Not Found" << endl;
}
}Binary Search – worsttime(n) – O(log n)
During each iteration, the size of the subarray searched is divided by 2 until success or failure occurs
basic strategy
$$
\begin{aligned}
& t^{} \ miidle =\frac{ first\ + (last-first)}{2} \
& if \ ( ^{}middler < value) \
& \ \ \ \ search \ from \ middle +1 \ to \ last -1; \
& else \ if \ (value < \ ^{*}middle ) \
& \ \ \ \ search \ from \ fisrt \ to \ midlle -1;\
&else \
& \ \ \ \ return \ true;
\end{aligned}
$$1
2
3
4
5
6
7
8
9
10
11
12template <typename T>
bool binary_search(T* first, T* last, const T& value){
if(first>=last)
return false;
T* middle = (first+last)/2;
if(*middle < value){
return binary_seach(middle+1,last,value);
}else if(value < *middle){
return binary_search(first,middle,value);
}
return true;
}
5. Vector and Deques
The vector, deque , and list classes are sequential-container classes: the items are stored in succession, from first to last.
5.1 Vector
The vector class is a “smart” array:
- random-access is supported with the index operator,[];
- there is a size method to keep track of the number of items;
- re-sizing is automatic
- there are methods to insert and delete at any position;
- there is an assignment operator to copy a vector to another vector;
- the vector class is templated , so you can restrict the type of items for a vector container;
- vector are as efficient as arrays
There are 32 methods in the vector class.
1 | template <typename T> |
Three pointer fields
start: with the address of the 0th location in the array;
finish: which points to the location just after the back item of the vector
end_of_storage: which points to the location just after the last location allocated for the array.
Insert method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33iterator insert(iterator position, const T& x)
{
size_type n = position - begin();
if(finish != end_of_storage)
{
if(position == end())
{
construct(finish,x);
finish++;
}
else
{
construct(finish,(*finish-1));
copy_backward(position,finish-1,finish);
*position = x ;
++finish;
}
}
else
{
size_type len = size()? 2*size(): static_allocatior.init_page_size();
iterator tmp = static_allocator.allocate(len);
uninitialized_copy(begin(),position,tmp);
construct(tmp+(position-begin()),x);
uninitialized_copy(position,end(),tmp+(position-begin())+1);
destroy(begin(),end());
static_allocator.deallocate(begin());
end_of_storage = tmp + len
finish = tmp + size() +1;
start = tmp;
}
return begin() + n;
}基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int main(){
vector<string> words;
string word;
for(int i=0; i<5; i++)
{
cin >> word;
words.push_back(word);
} // reading in words
words.erase(words.begin());
words.pop_back();
for(unsigned i=0; i<words,size(); i++)
{
cout << words[i] << endl;
}
}
5.2 High-Precision Arithmetic
The class very_long_int to support high-precision arithmetic
1 | class very_long_int |
5.3 Deques
6. Lists
A list, sometimes called a linked list, is a finite sequence of items such that:
accessing or modifying an arbitrary item in the sequence takes linear time;
given an iterator at position in the sequence, inserting or deleting at that position takes constant time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int main()
{
List<string> words;
List<string>::Iterator itr;
for(int i=0; i<5; i++)
{
word = (char)(i+'a');
words.push_back(word);
} // for
itr = words.begin();
itr++;
itr++;
words.insert(itr,"true");
itr = words,insert(itr,"good");
words.insert(itr,"right");
for(itr = words.begin(); itr!=words.end();itr++)
cout << *itr << endl;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51template <typename T>
class List{
private:
struct List_node{
List_node* next;
List_node* prev;
T data;
} // List_node
protected:
unsigned node;
list_node* head;
public:
classs Iterator{
friendly class List<T>;
protected:
list_node* node;
Iterator(list_node* x): node(x){}
public:
Iterator& operator--();
}
Iterator push_pront(const T& item);
Iterator push_back(const T& item);
void pop_front();
void pop_back();
bool empty();
int size();
Iterator begin();
Iterator end();
Iterator insert(Iterator position, const T& item);
void erase(Iterator position);
void erase(Iterator first, Iterator last); // [frist, last)
void splice(Iterator position, List<T>& x); // insert x befroe position
void sort();
}
template <typename T>
List<T>::Iterator List<T>::Iterator::operator--(){
node = node->prev;
return *this;
}
template <typename T>
List<T>::Iterator List<T>::insert(Iterator position, const T& item)
{
List_node* temp = new List_node;
temp->data = item;
temp->next = position->next;
temp->prev = position->prev;
postion->prev->next = temp;
position->prev = temp;
++length;
return temp;
}
7. Queue & Stacks
7.1 Queue
8.Binary & Search Tree
8.1 Binary Trees
A binary tree t is either empty or consists of an item, called the root item, and two distinct binary trees, called the left subtree and right subtree of t.
Empty
Only root (with two empty child)
less than or equal to 2 childs
Terminology
Leaf : an item whose left and right subtrees are empty
define the number of leaf
1
2
3
4
5
6if t is empty
leaves(t) = 0
else if t consists of a root item only
leaves(t) = 1
else
leaves(t) = leaves(leftTree(t))+leaves(rightTree(t))
branch : a line drawn from an item to its left and right subtree
expression tree : each leaf is an operand, and each non-leaf is operator
binary search tree
- each item in the left subtree is less than the root item
- each item in the right subtree is greater than the root item
- the left and right subtree are themselves binary search tree
descendant : the item’s child’s child … is the item’s descendant
path : a sequence of items in which each item except the last , is the parent of the next item in sequence.
height(t) : the number of branches from the root to the farthest leaf
define the method counting height
1
2
3
4
5if t is empty
height(t) = -1;
else
height(t) = max(heigt(leftTree(t)),height(rightTree(t))) + 1;
depth(x) / level(x) : the depth of an item x is the number of branches from the root to x
- define the number of depth(x)
1
2
3
4if x is the root item
depth(x) = 0
else
depth(x) = depth(parent(x)) +1two-tree
a biif\ t\ is\ full\ , nary tree t is two - tree if t is empty or if each non-leaf in t has two branches
Recursively speaking
a binary tree t is a two-tree if t is empty or if leftTree(t) and rightTree(t) are either both empty or both non-empty two-trees.
full-tree
binary tree t is full if t is a two-tree and all of the leaves of t have the same depth
Recursively speaking
a binary tree t is full if t is empty or if height(leftTree(t)) = height(rightTree(t)) and both leftTree(t) and rightTree(t) are full trees.
complete tree
- a binary tree t is complete if t is full through a depth of height(t)-1, and each leaf whose depth is height(t) as far less to left as possible
- a complete binary tree can be stored in an array
The binary-tree theorem – n(t) 节点数量
$$
leaves(t) <= \frac{n(t)+1}{2.0}
$$$$
\frac{n(t)+1}{2.0} <= 2^{height(t)}
$$$$
if\ t \ is\ a\ two\ tree\ ,\ leaves(t) = \frac{n(t)+1}{2.0}
$$$$
if\ leaves(t) = \frac{n(t)+1}{2.0}, t\ is a two \ tree
$$$$
if\ t\ is\ full\ , \ \frac{n(t)+1}{2.0} = 2^{height(t)}
$$$$
\ \frac{n(t)+1}{2.0} = 2^{height(t)} , \ if\ t\ is\ full\ ,\
height(t) = log_2(\frac{n(t)+1}{2.0})
$$The average time to insert, remove , search is logarithmic in n
8.2 External Path
Let t be a non-empty binary tree. e(t), the external path length of t, is the sum of the depths of all leaves in t.
The external path length theorem : let t be binary tree with k>0 leaves. then
$$
E(t) >= (k/2)floor(log_2k)
$$
8.3 Traversals of a binary tree
inOrder Left-Root-Right
1
2
3
4
5
6
7
8
9inOrder(t)
{
if(t is not empty)
{
inOrder(leftTree(t));
access the root item of t;
inOrder(rightTree(t));
}//if
}// inOrder traversalpostOrder Left-Right-Root
1
2
3
4
5
6
7
8
9
10postOrder(t)
{
if(t is not empty)
{
postOrder(leftTree(t))
postOrder(rightTree(t))
acess the root item of t;
}// if
}// postOrder traversalpreOrder(t) Root - Left - Right
1
2
3
4
5
6
7
8
9preOrder(t)
{
if(t is not empty)
{
access the root item of t;
preOrder(leftTree(t));
preOrder(rightTree(t));
}//if
}// preOrder traversal
8.4 Binary Search Trees
A binary search tree need not be full, complete or a two-tree, but it could be any of those. If a binary search tree is full or complete, its height is logarithmic in n.
The binary Tree class is an associative-container class , the location of an item is determined by comparing the item to items already in there .
The method interface for an minimal Binary Search Tree class:
1 | template <typename T> |
Use example
1 |
|
Find method: – O(log n)
a child pointer starts at the root, a parent pointer starts at the root, and a parent pointer starts at the header.
a loop descends the tree
if
child's item >= item sought
, child is replaced with the left child,and parent is replaced with childotherwise, child is replaced with its right child.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Iterator find(const T& item)
{
Link parent = header;
Link child = header->parent;
while(child!=NULL)
{
if(!(child->item<item))
{
parent = child;
child = child->right;
}
else
{
child = child->right;
}
if(parent == header || item< parent->item)
return end();
else
return parent;
}
}//find
The Insert method:
The item is inserted in a tree node to the left or right of parent
The header node’s left or right field is updated if the item is smallest or largrst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49Iterator insert(const T& item)
{
if(header->parent == NULL)
{
insertLeaf(item,header,header->parent);
}//inserting at tree's root
else
{
Link parent = header;
Link child = header->parent;
child(child!=NULL)
{
parent = child;
if(item < child->item)
{
child = child -> left;
}
else
{
child = child->right;
}
}// while
if(item<parent->item)
{
insertLeaf(item,parent,parent->left);
if(header->left==parent)
header->left = parent->left;
return parent->left;
}//insert at left of parent
else
{
insertLeaf(item,parent,parent->right);
if(header->right == parent)
header->right = parent -> right;
return parent->right;
}//insert at right of parent
} // tree is not empty
}//insert
void insertLeaf(const T& item,Link parent,Link& child)
{
child = new tree_node;
child->item = item;
child->parent = parent;
child->left = NULL;
child->right =NULL;
child->isHeader = false;
node_count++;
}//insertLeaf
The delete method
To delete the node that itr is position at, we need to make an adjustment to the parent of that node
1
2
3
4
5
6
7
8
9void erase(Iterator itr)
{
if(itr.link->parent->parent == itr.link) // itr at root node
deleteLink(itr.->parent->parent)
else if(itr.link->parent->left = itr.link)
deleteLink(itr.link->parent->left);
else
deleteLink(itr.link->parent->right);
}//erase1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void deleteLink(Link& link)
{
if(link->left == NULL || link->right == NULL) // non or only one child
prune(link);
else if(link->right->left==NULL) // 右孩子的左孩子为空
{
link->item = link->right->item;
prune(link->rigth);
}// link's right subtree has an empty left subtree
else // 右孩子的左孩子不为空
{
Link temp = link ->right -> left;
while(temp->left != NULL)
temp = temp -> left;
link->item = temp->item;
prune(temp->parent->left);
}// link's right subtree has a nonempty left subtree
}// deleteLink1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39void prune(Link& link)
{
Link linkCopy = link;
Link newLink;
node_count--;
if((link->left == NULL)&&(link->right==NULL))
{
if(link==header->left)
header->left = link->parent;
if(link==header->right)
header->right = link->parent;
link= NULL;
}//link 的项是树叶
else if(link->left == NULL)
{
link = link->right;
link->parent = linkCopy->parent;
if(linkCopy==header->left)
{
newLink = link
while(newLink->left!=NULL)
newLink = newLink->left;
header->left = newLink;
}
}//link->left为空
else
{
link = link -> left;
link->parent = linkCopy->parent;
if(linkCopy == header->right)
{
newLink = link;
while((newLink->right)!=NULL)
newLink = newLink->right;
header->right = newLink;
}
}// link-left非空
delete linkCopy;
}// prune
9. AVL Tree
9.1 AVL Tree
A tree-oriented data structure is balanced if its height is logarithmic in n.
Suppose we have an avl tree of height h with n items
$$
n>=min_{h} = fib(h+3) -1 > =(\frac{3}{2})^{h}\
n>=\frac{3}{2}^{h}\
h<=\frac{log_2n}{log_2\frac{3}{2}}\
$$
一共有四种旋转
右旋
旋转节点变为旋转节点左孩子的右孩子
旋转节点左孩子的友孩子变成旋转节点**(new)**的左孩子
左旋
旋转节点变成旋转节点右孩子的左孩子
旋转节点右孩子的左孩子变为旋转节点**(new)**的右孩子
先对需要旋转节点的左孩子左旋,再对需要旋转节点右旋(左旋右旋)
先对需要旋转节点的右孩子右旋,再对需要旋转节点左旋(右旋左旋)
An AVL tree is a binary search tree that either is empty or in which
The heights of the left and right subtrees differ by at most 1
The left and right substrees are avl trees.
Example

9.2 The AVLTree Class
Same as for BinSearchTree Class, but worstTime(n) is O(log n) for find ,insert, and erase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29template <class T, class Compare>
Class AVLTree {
private:
struct tree_node{
T item;
tree_node* parent;
tree_node* left;
tree_node* right;
bool isHeader;
char balanceFactor;
}
Compare compare;
tree_node* header;
long node_count;
public :
// worstTime(n) is constant
unsigned size();
Iterator begin();
Iterator end();
// worstTime(n) is O(log n)
Iterator find(const T& item);
void insert(const T& item);
void erase(Iterator position);
// worstTime(n) is O(n)
~AVLTree();
}The AVLTree Class allows users determine how to order the node data
The function class, is a class in which the function call operator, operator(), has been overload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// Function class example
class Sample
{
public:
double operator()(int i)
{
return 1.0/i;
}
}; // class Sample
class ByLength
{
public:
bool operator()(const string& s1, const string& s2)
{
return s1.length() < s2.length();
}//operator
}// class ByLength
template <class T>
struct less:binary_function<T,T,bool>
{
bool operator()(const T& x, const T& y)const{
return x>y;
}
}
int mian(){
Sample f; // f is a function object
cout << f(39) << endl;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26int main(){
AVLTree<string,ByLength> avl1; // 比较字符串长度
avl1.insert("yes");
avl1.insert("no");
avl1.insert("maybe");
AVLTree<string,ByLength>::Iteraor itr1;
for(itr1 = avl1.begin();itr1!=avl1.end();itr1++)
{
cout << *itr1 << endl;
}
AVLTree<string,less<string>> avl2; // 比较字母大小
avl2.insert("yes");
avl2.insert("no");
avl2.insert("maybe");
AVLTree<string,less<string>>::Iterator itr2;
for(itr2=avl2.begin();itr2!=avl2.end();itr2++)
{
cout << *itr2 << endl;
}
return 0;
}
/**
The output:
no yes maybe
maybe no yes
*/The insert method
The ancestor node – has a balanceFactor of ‘L’ or ‘R’ and is closest to where the new item will be added.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34void inser& item)
{
tree_node* parent = header,
child = header->parent;
ancestor = NULL;
while(child!=NULL)
{
parent = child;
if(child->balanceFactor != '=')
{
ancestor = child;
if(compare (item,child->item)) // less(x,y){return x<y}
child->child->left;
else
child = child->right;
}
}
if(compare(item,parent->item))
{
insertItem(item,parent,parent->left);
fixAfterInsertion(ancestor,parent->left);
if(header->left == header)
header->left = parent->left;
return parent->left;
}
else
{
insertItem(item,parent,parent->item);
fixAfterInsertion(ancestor,parent->right);
if(header -> right == parent)
header->rright == parent->right;
return parent->right;
}
}SIX cases
case 1: ancestor is NULL – 直接插入,无需调整
case 2: ancestor->balanceFactor is ‘L’ and the insertion was in ancestor’s right subtree – 直接擦插入无需调整
case 3: ancestor->balanceFactor is ‘R’ and the inserted item is in the right subtree – 正常插入节点后对ancestor 节点进行左旋
case 4 : ancestor->balanceFactor is ‘L’ and the inserted item is in the left subtree of the left subtree of ancestor – 正常插入节点后对ancestor 节点进行右选
case 5: ancestor->balanceFactor is ‘L’ and the inserted item in the right subtree of the left subtree of ancestor – 先对ancestor->left 进行左旋,再对ancestor 进行右旋
case 6: ancestor->balanceFactor is ‘R’ and the inserted item is in the left subtree’s of the right subtree of ancestor – 先对ancestor->right进行右旋,再对ancestor进行左旋
Conclusion
9. Red-Black Trees
9.1 Basically definition
A red-black tree is a binary search tree in which the root item is colored black, every other item is colored red or black , and
red rule : a red item cannot have any red children
path rule : the number of black items is the same in any path from the root item to an item with no children or one child.
red-black tree is not a avl tree
$$
log_2(n)<=height <= 2log_2(n)
$$example
The rb_tree class
two coloring convention:
- header’s node is red
- when item is inserted , it is colored red initially
There is a special node(pointer) NIL
- NIL’s color_field is black
- NIL’s left_link and right_link fields are NULL
- NIL’s parent_link is initially NULL.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19template <typename Value>
class rb_tree {
private:
enum color_type = {red,black};
struct rb_tree_node
{
color_type color_field;
rb_tree_node* parent_link;
rb_tree_node* left_link;
rb_tree_node* right_link;
Value value_field;
}
public:
color_type color(rb_tree_node node);
rb_tree_node parent(rb_tree_node node);
rb_tree_node root();
Iterator insert(const VALUE& v);
void erase(Iterator position);
}
9.2 Insertion Method
There are five steps to accomplish the insertion of the insertion of the item v into an rb_tree object
worstTime(n) and averageTime(n) is O(log n)
create a node pointed to by x;
set value(x) to v;
Insert *x as a leaf(with a BinSearchTree-style insertion), and set color(x) to red;
Fix up the tree by re-coloring and restructring, if the red rule is violated(path rule ok : red insertion); 最开始调整是因为红色规则违反问题
set color(root()) to black
The step 4 detail needed
Not if x(the node pointed to by x) is the root, because then step 5 will set x’s color to black
if color(parent(x)) is black, then no restructuring is needed because the red rule is not violated
parent(x) is left child(action depends on the sibling of parent(x))
let y be the sibling of parent(x)
case 1– color(y)==color_type. red
把插入节点父母和父母的姐妹变成黑色,把插入节点父母的父母变成红色,再把插入节点变成父母的父母的颜色(显示变为红色)
case 2: color(y) == color_type. black, and x is a right child – y is NIL
将x(指向插入节点位置的变量)赋值为x的父母,在将x左旋,达到情况3
case 3: color(y) == color_type. black , and x is a left child – y is NIL
将x(经过case 2 的赋值已经指向原来插入位置的副节点)的父母的颜色赋值为黑色,将x父母的父母的颜色赋值为红色,再右旋x父母的父母
parent(x) is right child(action depends on the sibling of parent(x))
let y be the sibling of parent(x)
case 1: color(y) == color_type. red
把插入节点父母的父母变成红色,把插入节点父母和父母的姐妹变成黑色,把插入几点显示变为与父母的父母相同的颜色
case 2: color(y) == color_type. black and x is a left child – y is NIL
将x(指向插入节点的位置),在将x右旋,达到情况3
case 3: color(y) == color_type. black and x is a right child – y is NIL
将x的父母赋值为红色,再将x的父母的父母赋值为黑色,x的父母的父母左旋
Exercise
9.3 Erase Method
It turns out that there is work to be done only if
worsTime(n) is O(log n) , averageTime(n) is constant , amortizedtime(n) is constant
The deleted node is a black leaf
The deleted node has two children and replacement node is a black leaf
In either case, the replacement node is itself replaced by a NIL node
Let x be that NIL node, let w be the siblings of x
Example
The detail of erase method
1
2
3
4
5
6
7
8
9
10
11
12while(x!=root()&& color(x)==black)
{
if(x==left(parent(x)))
{
w = right(parent(x));
if(w' s color is red) {...case1...};
if(w' s children are both black) {{....case 2...}}
else if (right(w) is black ){...case 3 ...}
else if(righe(w) is red){...case 4 ...}
}
}//while
color(x) = black;when x is a left child
case 1: color(w) = red
先找到替代节点位置x,再将w赋值为黑色,x的父母为红色,对x的父母进行左旋,将w赋值为x现在的姐妹节点
1
2
3
4color(parent(x)) = red;
color(w) = black;
left_rotated(parent(x)); // x 少了一个左孩子,所以进行左旋
w = rigth(parent(x));case 2: color(w) = black and both of w’ s children are black
case 1 的结果满足这种情况,将w赋值为红色,x赋值到x的父母的位置
1
2color(w) = red;
x = parent(x);case 3: color(w) = black and color((right(w))) = black
如果满足case 2, 先对case 2 进行处理,处理后仍满足case 3, 则将w的左孩子赋值为黑色,再将w的赋值为红色,对w进行右旋,w更新为旋转后x的父母右孩子
1
2
3
4color(w) = red;
color(left(w)) = color(w);
right_rotate(w); // left(w)变黑后左边子树更高,因此需要右转
w = right(parent(x));case 4: color(w) = black ,and color(right(w)) = red
将w赋值为x父母的颜色,将x的父母的颜色和w右孩子的颜色赋值为黑色,将x的父母左旋,退出循环
1
2
3
4
5color(w) = color(parent(x));
color(parent(x)) = black;
color(right(w)) = black;
left_rotate(parent(x));
break;when x is a right child
case 1: color(w) = red
先找到替代节点位置x,再将w赋值为黑色,x的父母赋值为红色,对x的父母进行右旋,将w赋值为x现在的姐妹节点
1
2
3
4color(parent(x)) = red;
color(w) = black;
right_rotate(parent(x));
w = left(parent(x));case 2: color(w) = black and w’ s children are black
将w赋值为红色,将x指向x的父母
1
2color(w) = red;
x = parent(x);case 3: color(w) = black and color(left(w)) = black
将w赋值为红色,将x的右孩子赋值为黑色,对w进行左旋,再将w更新为旋转后的x的父母的左孩子
1
2
3
4color(left(w)) = red;
color(right(w)) = black;
leftr_rotate(w);
w = left(parent(x));case 4: color(w) = black, and color(left(w)) = red
将w变为w父母的颜色,将w的右孩子和w的父母变为黑色,将w父母(也是x的父母)右旋,退出循环
1
2
3
4
5color(w) = color(parent(x));
color(parent(x)) = black;
color(left(w)) = black;
right_rotate(parent(x));
break;
Exercise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65while(x!=root() && color(x) == black)
if(x==left(parent(x)))
{
link_type w = lef(parent(x));
if(color(w) == red) // case 1
{
color(parent(x)) = red;
color(w) = black;
rotate_left(parent(x));
w = right(parent(x));
}
if(color(right(w))==black && color(left(w))==black) // case2
{
color(w) = red;
x = parent(x);
}
else
{
if(color(right(w))==black) // case 3
{
color(left(w))=black;
color(right(w)) = red;
rotate_right(w);
w = rigth(parent(x));
}
else // case 4
{
color(w) = color(parent(x));
color(parent(x)) = black;
color(right(w)) = black;
rotate_left(parent(x));
break;
}
}
}else{
link_type w = left(paren(x));
if(left(w)==red)
{
color(w) = black;
color(parent(x)) = red;
rotate_right(parent(x));
w = left(parent(x));;
}
if(color(right(w))==black && color(left(w))==black)
{
color(w) = red;
x = parent(x);
}else{
if(left(w)=black)
{
left(w) = red;
right(w) = black;
rotate_left(w);
w = left(parent(w));
}else
{
color(w) = color(parent(x));
color(parent(x)) = black;
color(left(w)) = black;
rotate_right(parent(x));
break;
}
color(x) = black;
}
}
- Title: DataStructure
- Author: Francessca
- Created at : 2024-12-29 11:44:03
- Updated at : 2024-12-29 14:26:32
- Link: https://francesscawy.github.io/2024/12/29/DataStruture/
- License: This work is licensed under CC BY-NC-SA 4.0.