数据结构和算法学习笔记

本文详细介绍了数据结构的基础知识,包括逻辑结构和物理结构,重点讲解了堆栈、队列的实现,以及使用堆栈实现进制转换。接着,文章深入探讨了多种排序算法,如冒泡排序、插入排序、选择排序、快速排序和归并排序,分析了它们的时间复杂度和特性。此外,还介绍了线性查找和二分查找这两种查找算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


使用建议:建议使用 gedit 软件查看本笔记,可以让代码中的关键字、注释等高亮显示,增强可读性;
另外建议把 gedit 软件首选项里的制表位设置为 4(默认为 8),代码中的缩进就会更合理;字体建议用 13 号。

《目录》
DAY01---逻辑结构,物理结构,数据结构基本运算,堆栈,顺序表堆栈:任意进制转换(练习),链式表堆栈
DAY02---队列,顺序表队列,链式表队列,堆栈实现队列效果,链表,练习:实现一个单向线性链表,二叉树,有序二叉树
DAY03---算法,冒泡排序,插入排序,选择排序,快速排序,自写 qsort,归并排序,线性查找,二分查找

**************************************************************

DAY01-MIN

1.    逻辑结构
    1)集合结构(集):结构中的元素除了同属一个集以外,没有任何联系;
    2)线性结构(表):结构中的元素具有一对一的前后关系;
        特点:同一性,有穷性,有序性
    3)树型结构(树):结构中的元素具有一对多的父子关系;
    4)网状结构(图):结构中的元素具有多对多的关系,交叉映射;
    
    问:三种基本的数据模型 ?
    答:按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。

2.    物理结构
    1)顺序结构(数组):结构中的元素存放在一组连续的地址空间中;
        优点:随机访问方便;
        缺点:需要连续内存,空间利用率低;为保证地址连续性,进行插入、删除的时候,会带动大量数据移动;
    2)链式结构(链式):结构中的元素存放在彼此独立的地址空间中,
    每个独立的地址空间被称为节点,节点中除了保存数据以外,还保存了另外一个或者几个相关节点的地址和本节点的相关属性;
        优点:充分利用零散碎片,空间利用率高;插入、删除方便;
        缺点:随机访问不方便;

3.    物理结构和逻辑结构关系
                 表          树               图
        顺序        数组        顺序树        复合结构
        链式    链表      链式树        复合结构

4.    数据结构基本运算
    1)创建与销毁
    2)插入与删除
    3)获取与修改
    4)排序与查找

5.    堆栈
    1)基本特征:后进先出;
    2)基本操作:压入(push),弹出(pop);
    3)实现要点:a.初始化空间,b.栈顶指针(不一定是指针,数组下标也可以),c.判断空满;
    
6.    基于顺序表的堆栈:实现对一个整数进行任意进制的转换
    
    class Stack {
        public:
            // 构造中分配内存
            Stack(size_t size = 10) : m_array(new int[size]),
                                        m_size(size), m_top(0) {}
            // 析构内存
            ~Stack() {
                if(m_array) {
                    delete[] m_array;
                    m_array = 0;
                }
            }
            // 压入
            void push(int data) {
                if (full())                    // 判满
                    throw OverFlow();
                m_arrary[m_top++] = data;
            }
            // 弹出
            int pop() {
                if (empty())
                    throw UnderFlow();
                return m_arrary[--m_top];
                // 这里是前--,不是后--,如果是后--,则弹出的是 m_top 的下一个元素,不存在;
            }
            // 判满
            bool full() const {
                return m_top >= m_size;
            }
            // 判空
            bool empty() const {
                return ! m_top;
            }
        private:
            // 上溢异常(内部类实现)
            class OverFlow : public exception {
                const char* what() const throw() {
                    return "堆栈上溢!";
                }
            };
            // 下溢异常
            class UnderFlow : public exception {
                const char* what() const throw() {
                    return "堆栈下溢!";
                }
            };
            int* m_array;        // 数组
            size_t m_size;        // 最大容量
            size_t m_top;        // 栈顶
    };
    // 实现进制转换
    void printb(unsigned int numb, int base) {
        Stack stack(256);
        do {                                            // 用 do 排除 0 的情况
            stack.push(num % base);
        } while (num /= base);
        while(! stack.empty()) {
            int digit = stack.pop();
            if(digit < 10)
                cout << digit;
            else
                cout << char(digit - 10 + 'A');
        }
        cout << endl;
    }
    int main() {
        // 测试顺序堆栈
        try {
            Stack stack;
            for(int i = 0; ! stack.full(); ++i)        // 放入
                stack.push(i);
            //    stack.push(0);                        // 测试用,满了继续放入一个数据;
            while(! stack.empty())                    // 弹出
                cout << stack.pop() << endl;
            //    stack.pop();                            // 测试用,弹完了继续弹;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        // 测试进制转换
        cout << "输入一个整数:" << flush;
        int numb;
        cin >> num;
        cout << "您想看几进制:" << flush;
        int base;
        cin >> base;
        cout << "结果:";
        print(numb, base);
        return 0;
    }    
    
7.    基于链式表的堆栈:
    
    class Stack {
        public:
            // 构造过程中初始化为空栈
            Stack() : m_top(NULL) {}
            // 析构过程中释放剩余节点
            ~Stack() {
                for(Node* next; m_top; m_top = next) {
                    next = m_top->m_next;
                    delete m_top;
                }
            }
            // 压入
            void push(int data) {
                /*
                Node* node = new Node;
                node->m_data = data;
                node->m_next = m_top;
                m_top = node;
                */
                m_top = new Node(data, m_top);
            }
            // 弹出
            int pop() {                                // 把节点的数据和指针都分别取出来,然后操作;
                if(empty())
                    throw UnderFlow();
                int data = m_top->m_data;
                Node* next = m_top->m_next;
                delete m_top;
                m_top = next;
                return data;
            }
            bool empty() const {                        // 如果为空返回真 1;
                return ! m_top;                        // m_top 为 0 即为空,取反作为返回值;
            }
        private:
            // 下溢
            class UnderFlow : public exception {
                const char* what() const throw() {
                    return "堆栈下溢!";
                }
            };
            // 节点
            class Node {
                public:
                    Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
                    int m_data;                        // 数据
                    Node* m_next;                        // 下一个节点指针
            };
            Node* m_top;
    };    
    int main() {
        try {
            Stack stack;
            for(int i = 0; i < 5; ++i)                // 放入
                stack.push(i);
            // stack.push(0);                            // 测使用,满了继续放入一个数据;
            while(! stack.empty())                    // 弹出
                cout << stack.pop() << endl;
            // stack.pop();                            // 测使用,弹完了继续弹;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
    }
    
*********************************************************
    
DAY02-MIN

1.    队列    
    1)基本特征:先进先出;
    2)基本操作:压入(push)、弹出(pop);
    3)实现要点:a.初始化空间,b.从前端(front)弹出,从后端(rear)压入;c.循环使用;d.判空判满;
    
2.    基于顺序表的队列
    class Queue {
        public:
            Queue(size_t size = 5) : m_array(new int[size]), m_size(size),
                                        m_rear(0), m_front(0), m_count(0) {}
            ~Queue() {
                if(m_array) {
                    delete[] m_array;
                    m_array = NULL;
                }
            }
            void push(int data) {
                if(full())
                    throw OverFlow();
                if(m_rear >= m_size)            // 如果后端指针超出数组大小,重新置零;(可以用 %m_size 实现)
                    m_rear = 0;
                ++m_count;                    // 计算放入数据个数
                m_arrary[m_rear++] = data;    // 放入数据,同时指针上移一位;
            //    m_arrary[(m_rear %= m_size )++]=data;
            }
            void pop() {
                if(empty()) {
                    throw UnderFlow();
                if(m_front >= m-size)
                    m_front = 0;
                --m_count;                    // 计算弹出数据个数;
                return m_array[m_front++];
                }
            }
            bool empty() const {
                return ! m_count;                // 计数器为 0,则为空;
            }
            bool full() const {
                return m_count == m_size;    // 压入于弹出数据之差为空间大小,则满;
            }
        private:
            class OverFlow : public exception {
                const char* what() const throw() {
                    return "队列上溢";
                }
            };
            class UnderFlow : public exception {
                const char* what() const throw() {
                    return "队列下溢";
                }
            };
            int* m_array;        // 数组
            size_t m_size;    // 容量
            size_t m_rear;    // 后端
            size_t m_front;    // 前端
            size_t m_count;    // 计数器,判空判满用
    };
    int main() {
        try {
            Queue queue;
            queue.push (5);
            queue.push (10);
            cout << queue.pop();        // 输出:5
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        return 0;
    }
    
3.    基于链式表的队列
    class Queue {
        public:
            Queue() : m_rear(NULL), m_front(NULL) {}
            ~Queue() {
                for(Node* next; m_front; m_front = next) {
                    next = m_front->m_next;
                    delete m_front;
                }
            }
            void push(int data) {
                Node* node = new Node(data);
                if(m_rear)
                    m_rear->m_next = node;
                else
                    m_front = node;
                m_rear = node;
            }
            int pop() {
                if(empty())
                    throw UnderFlow();
                int data = m_front->m_data;
                Node* next = m_front->m_next;
                delete m_front;
                if(! (m_front = next))
                    m_rear = NULL;
                return data;
            }
            bool empty() const {
                return ! m_front && ! m_rear;
            }
        private:
            class UnderFlow : public exception {
                const char* what() const throw() {
                    return "队列下溢";
                }
            };
            class Node {
                public:
                    Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
                    int m_data;        // 数据
                    Node* m_next;        // 后指针
            };
            Node* m_rear;                // 后端
            Node* m_front;            // 前端
    };
    int main() {
        try {
            Queue queue;
            for(int i = 0; i < 20; ++i)
                queue.push(i);
            while(! queue.empty())
                cout << queue.pop() << endl;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        return 0;
    }

4.    用堆栈的先进后出效果实现队列的先进先出效果:
    把上面链式堆栈改作头文件 lstack.h,内容如下:
    #ifndef LSTACK_H
    #define LSTACK_H
    #include <stdexcept>    
    class Stack {
        public:
            Stack() : m_top(NULL) {}
            ~Stack() {
                for(Node* next; m_top; m_top = next) {
                    next = m_top->m_next;
                    delete m_top;
                }
            }
            // 压入
            void push(int data) {
                m_top = new Node(data, m_top);
            }
            // 弹出
            int pop() {
                if(empty())
                    throw UnderFlow();
                int data = m_top->m_data;
                Node* next = m_top->m_next;
                delete m_top;
                m_top = next;
                return data;
            }
            bool empty() const {
                return ! m_top;
            }
        private:
            // 下溢
            class UnderFlow : public exception {
                const char* what() const throw() {
                    return "堆栈下溢!";
                }
            };
            // 节点
            class Node {
                pulic:
                    Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
                    
                    int m_data;        // 数据
                    Node* m_next;        // 下一个节点指针
            };
            Node* m_top;
    };
    #endif
    
    下面写 queue.cpp 文件:
    #include "lstack.h"
    #include <iostream>
    #include <stdexcept>
    using namespace std;
    class Queue {
        public:
            void push(int data) {
                m_i.push(data);
            }
            int pop() {
                if(m_o.empty()) {
                    if(m_i.empty())
                        throw underflow_error("队列下溢");    // 标准库函数
                    while(! m_i.empty())
                        m_o.push(m_i.pop());
                }
                return m_o.pop();
            }
            boll empty() const {
                return m_i.empty() && m_o.empty();
            }
        private:
            Stack m_i;    // 输入栈
            Stack m_o;    // 输出栈
    };
    int main() {
        try {
            Queue queue;
            for(int i = 0; i < 20; ++i)
                queue.push(i);
            while(! queue.empty())
                cout << queue.pop() << endl;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        return 0;
    }

5.    链表
    1)基本特征:内存中不连续的节点序列,彼此之间通过指针关联;
        前指针(prev)指向前节点,后指针(next)指向后节点;
    2)基本操作:追加、插入、删除、遍历;
    3)实现要点:略;
    
    #include <stdexcept>
    class List {
        public:
            List() : m_head(NULL),m_tail(NULL) {}
            ~List() {
                for(Node* next; m_head; m_head = next) {
                    next = m_head->m_next;
                    delete m_head;
                }
            }
            // 追加(在最后插入)
            void append(int data) {
                m_tail = new Node(data, m_tail);
                if(m_tail->m_prev)
                    m_tail->m_prev->m_next = m_tail;
                else
                    m_head = m_tail;                    // 相当于初始化 m_head;
            }
            // 前插入
            void insert(size_t pos, int data) {
                for(Node* find = m_head; find; find = find->next)
                    if(pos-- == 0) {
                        Node* node = new Node(data, find->m_prev, find);
                        if(node->m_prev)
                            node->m_prev->m_next = node;
                        else                            // pos 直接为 0 的情况;
                            m_head = node;
                        find->m_prev = node;
                        // node->m_next->m_prev = node;
                        return;
                    }
                // pos 越界
                throw out_of_range("链表越界");        // 标准库
            }
            // 删除
            void erase(size_t pos) {
                for(Node* find = m_head; find; find = find->next)
                    if(pos-- == 0) {
                        if(find->m_prev)                // 有前节点;也就是说 pos 不指向第一个;
                            find->m_prev->m_next = find->m_next;
                        else
                            m_head = find->m_next;
                        if(find->m_next)                // 有后节点;也就是说 pos 不指向最后一个;
                            find->m_next->m_prev = find->m_prev;
                        else
                            m_tail = find->m_prev;
                        delete find;
                        return;
                    }
                throw out_of_range("链表越界");
            }
            // 正向遍历
            void forward() const {
                for(Node* node = m_head; node; node = node->next)
                    cout << node->m_data << ' ';
                cout << endl;
            }
            // 反向遍历
            void backward() const {
                for(Node* node = m_tail; node; node = node->prev)
                    cout << node->m_data << ' ';
                cout << endl;
            }
            // 下标访问
            int& operator[] (size_t index) {
                for(Node* find = m_head; find; find = find->m_next)
                    if(index-- == 0) {
                        return find->m_data;
                    }
                throw out_of_range("链表越界");
            }
            const int& operator[] (size_t index) const {
                return const_cast<list&>(*this)[index];
            }
        private:
            class Node {
                public:
                    Node(int data = 0, Node* prev = NULL,
                        Node* next = NULL) : m_data(data),
                        m_prev(prev), m_next(next) {}
                    int m_data;
                    Node* m_prev;
                    Node* m_next;
            };
            Node* m_head;        // 头指针(链表)
            Node* m_tail;        // 尾指针(链表)
    };
    int main() {
        try {
            List list;
            list.append(10);
            list.append(10);
            list.append(10);
            list.insert(1, 15);
            list.forward();
            list.backward();
            list.erase(1);
            for(size_t i= 0; i < 3; ++i)
                ++list[i];                    // 每个元素 +1
            const List& cr = list;
            for(size_t i= 0; i < 3; ++i)
                cout << cr[i] << ' ';        // 上有 const,则返回右值,如果写成 ++cr[i] 报错;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        return 0;
    }
    
6.    练习:实现一个单向线性链表
    要求:
        1)建立 append()、测长 size()、正反向打印 print()/rprint();
        2)逆转头尾列表指针 reverse();
        3)获取中间节点值(如果是偶数个,获取靠左那个),只允许遍历一次 middle();
            思路:定义两个变量,mid 一次跳一个,node 一次跳两个;node 到头,mid 刚好到中间;
        4)有序链表的插入和删除(根据值删除,如果有 3 个 5,则都删除)
    class List {
        public:
            List() : m_head(NULL),m_tail(NULL) {}
            ~List() {
                for(Node* next; m_head; m_head = next) {
                    next = m_head->m_next;
                    delete m_head;
                }
            }
            void append(int data) {
                Node* node = new Node(data);
                if(m_tail)
                    m_tail->m_next = node;
                else
                    m_head = node;
                m_tail = node;
            }
            size_t size() const {
                size_t cn = 0;
                for(Node* node = m_head; node; node = node->m_next)
                    ++cn;
                return cn;
            }
            void print() const {
                for(Node* node = m_head; node; node = node->m_next)
                    cout << node->m_data << ' ';
            }
            // 构造两个堆栈,数据反反得正,或者构造递归
            void rprint() const {
                rprint(m_head);
                cout << endl;
            }
            /*
            void reverse() {
                reverse(m_head);
                swap(m_head, m_tail);
            }
            */
            void reverse () {
             if(m_head != m_tail) {
                 Node* p1 = m_tail = m_head;
                 Node* p2 = p1->m_next;
                 Node* p3 = p2->m_next;
                 for(p1->m_next = NULL; p3; p3 = p3->m_next) {
                     p2->m_next = p1;
                     p1 = p2;
                     p2 = p3;
                 }
                 (m_head = p2)->m_next = p1;
             }
            }
    
            int middle() const {
                if(! m_head || ! m_tail)
                    throw underflow_error("链表下溢");
                if(m_head == m_tail)
                    return m_head->m_data;
                Node* mid = m_head;
                for(Node* node = m_head; node->m_next && node->m_next->m_next; node = m_next->m_next)
                    mid = mid->m_next;
                return mid->m_data;
            }
            // 插入data,继续保证有序性
            void insert(int data) {
            
            }
            // 删除所有 data
            void remove(int data) {
            
            }
        private:
            class Node {
                public:
                    Node(int data = 0, Node* next = NULL) : m_data(data), m_next(next) {}
                int m_data;
                Node* m_next;
            };
            // 递归
            void rprint(Node* head) const {
                if(head) {
                    rprint(head->m_next);
                    cout << head->m_data << ' ';
                }
            }
            /*
            void reverse(Node* head) {
                if(head && head->m_next) {
                    reverse(head->m_next);
                    head->m_next->m_next = head;
                    head->m_next = NULL;
                }
            }
            */
            Node* m_head;        // 头指针(链表)
            Node* m_tail;        // 尾指针(链表)
    };
    int main() {
        try {
            for(int i = 0; i < 9; ++i)
                list.append(i);
            cout << list.size() << endl;
        }
        catch(exception& ex) {
            cout << ex.what() << endl;
            return -1;
        }
        return 0;
    }
    
7.    二叉树
    1)基本特征:树型结构的最简模型,每个节点最多有两个子节点;
    2)单根性:除了根节点外,每个节点有且仅有一个父节点,整棵树只有一个根节点;
    3)递归结构:用递归处理二叉树问题可以简化算法;
    4)基本操作:生成、遍历
        L-D-R:中序遍历
        D-L-R:前序遍历
        L-R-D:后序遍历
        
8.    有序二叉树(二叉搜索数)
    L <= D <= R    (大于节点,放右边;小于节点,放左变)
    50    70    20    60    40    30    10    90    80
    把上面的九个数字构造成有序二叉树:
                50
              /   \
            20        70
          /   \   /  \
         10    40 60   90
               /      /
              30         80
    思路:50 为根,70 > 50,放右边;20 > 50,放左边;
    60 > 50,右边;60 < 70,左边;40 < 50,左边;40 > 20,右边;以此类推;
    
    class Tree {
        public:
            Tree() : m_root(NULL), m_size(0) {}
            // 销毁剩余节点
            ~Tree() {
                clear();
            }
            // 插入数据
            void insert(int data) {
                insert(new Node(data), m_root);    //
                ++m_size;
            }
            // 删除第一个匹配数据,不存在返回 false
            void erase(int data) {
                Node*& node = find(data, m_root);
                if(node) {
                    // 首先左子树插入右子树
                    insert(node->left, node->m_right);
                    // 右子树提升到节点处
                    Node* temp = node;
                    node = node->m_right;
                    delete temp;
                    --m_size;
                    return true;
                }
                return false;
            }
            // 删除所有匹配数据
            void remove(int data) {
                while(erase(data));
            }
            // 清空树
            void clear() {
                clear(m_root);
                m_size = 0;
            }
            // 将所有 _old 替换为 _new
            void updata(int _old, int _new) {
                while(erase(_old))        //
                    insert(_new);
            }
            // 判断 data 是否存在
            bool find(int data) {
                return find(data, m_root) != NULL;
            }
            // 中序遍历
            void travel() {
                travel(m_root);
            }
            // 获取树大小
            size_t size() {
                return m_size;
            }
            // 获取树高度
            size_t height() {
                return height(m_size);
            }
        private:
            // 节点
            class Node {
                public:
                    Node(int data) : m_data(data), m_left(NULL), m_right(NULL) {}
                    int m_data;        // 数据
                    Node* m_left;        // 指向左子节点或左子树
                    Node* m_right;
            };
            void insert(Node* node, Node*& tree) {        // 节点和子树
                if(! tree)
                    tree = node;            // 建立第一个节点或递归终止
                else if(node) {
                    if(node->m_data < tree->m_data)
                        insert(node, tree->m_left);
                    else
                        insert(node, tree->m_right);
                }
            }
            // 返回子树 tree 中值于 data 相匹配的节点的父节点中指向该节点的成员变量的别名
            Node*& find(int data, Node*& tree) {
                if(! tree)
                    return tree;                        // tree = NULL;
                else if(data == tree->m_data)
                    return tree;                        // 匹配
                else if(data < tree->m_data)        // 左子树中找
                    return find(data, tree->m_left);
                else
                    return find(data, tree->m_right);
            }
            void clear(Node*& tree) {
                if(tree) {
                    clear(tree->m_left);
                    clear(tree->m_right);
                    delete tree;
                    tree = NULL;
                }
            }
            // 遍历子树
            void travel(Node* tree) {
                if(tree) {
                    travel(tree->m_left);
                    cout << tree->m_data << ' ';
                    travel(tree->m_right);
                }
            }
            size_t height(Note* tree) {
                if(tree) {
                    size_t lh = height(tree->m_left);
                    size_t rh = height(tree->m_right);
                    return (lh > rh ? lh : rh) +1;
                }
                return 0;
            }
            Node* m_root;        // 树根
            size_t m_size;    // 树大小
            
    };
    int main() {
        Tree tree;
        tree.insert(50);
        tree.insert(70);
        tree.insert(20);
        tree.insert(90);
        tree.travel();
        cout << tree.size() << tree.height();
        tree.earse(70);
        tree.update(20, 80);
        tree.find(20);
        tree.clear();
        return 0;
    }

************************************************************
    
DAY03-MIN

1.    程序设计 = 数据结构 + 算法 + 设计方法学
    数值算法(科学/工程计算):微积分、方程组、有限元分析、神经网络等;
    非数值算法(系统开发/编程):查找、排序、决策、调度;
    
2.    冒泡排序
    步骤:
    1)比较相邻的元素,如果第一个比第二个大,就交换他们;
    2)对每一对相邻的元素做同样比较,使得最后元素为最大值,需要 n-1 次比较;
    3)对所有元素重复相同的工作,除了最后一个;
    这样参加比较的元素会越来越少,直到全部比较完,那么需要 n-1-i 次;
    4)极端情况:如果数组是下面这种情况
        1 2 3 4 5 7 6
    那么,这种情况下我们实际只需要比较 1 次就可以了,不需要 n-1 次,
    所以下面的例子里加入了 bool 判断;
    
    void bubbleSort(int data[], size_t size) {
        for(size_t i = 0; i < size -1; ++i) {
            bool ordered = true;                                // 用于判断这种情况:1 1 1 2 3 4 5 7 6
            for(size_t j = 0; j < size - 1 - i; ++j)
                if(data[j+1] < data[j]) {
                    int temp = data[j+1];
                    data[j+1] = data[j];
                    data[j] = temp;
                    ordered = false;
                }
            if(ordered)
                break;
        }
    }
    int main() {
        int data[] = {3, 2, 23, 4, 65, 12, 6};
        size_t size = sizeof(data) / sizeof(data[0]);
        bubbleSort(data, size);
        for(size_t i = 0; i < size; ++i)
            cout << data[i] << ' ';
        cout << endl;
    }
    
    冒泡排序特点:
    本例题的平均时间复杂度:O(n^2); 随着样本增加,时间复杂度会急剧上升;(抛物线)    
    稳定性:排序过程中,如果两个数据相同,则不改变位置,称为稳定排序;
    敏感性:对数据的有序性非常敏感,就是对数据原有的顺序很在意;

4.    插入排序
    步骤:
    1)从第二个开始,和前面的每一个相比;
    2)如果比前一个大/相等,则排在前一个后面;
    3)如果比前一个小,则把前一个数后移一位;继续比较,以此类推;
    
    void insertSort(int data[], size_t size) {
        for(size_t i = 1; i < size; ++i) {
            int temp = data[i];
            size_t j;
            for(j = i; j > 0 && temp < data[j-1]; --j)        // 比前一个小,则后遗;仅仅确定插入位置
                data[j] = data[j-1];
            if(j != i)                                        // 相等的情况
                data[j] = temp;                                // 执行插入动作;
        }
    }
    int main() {
        int data[] = {3, 2, 23, 4, 65, 12, 6};
        size_t size = sizeof(data) / sizeof(data[0]);
        insertSort(data, size);
        for(size_t i = 0; i < size; ++i)
            cout << data[i] << ' ';
        cout << endl;
    }
    
    插入排序特点:
    本例题的平均时间复杂度:O(n^2); 和冒泡一样;
    冒泡排序内层 for 循环需要三次赋值,插入排序只是一次赋值;速度略优于冒泡;
    稳定性:稳定,和冒泡一样;
    敏感性:敏感,和冒泡一样;
    
5.    选择排序
    步骤:
    1)选出最小的,和第一个作交换;
    2)剩下的选出最小的,和第二个作交换;    
    
    void selectSort(int data[], size_t size) {
        for(size_t i = 0; i < size -1; ++i) {
            size_t min = i;
            for(size_t j = i; j < size; ++j)
                if(data[j] < data[min]
                    min = j;
            if(min != i) {
                int temp = data[i];
                data[i] = data[min];
                data[min] = temp;
            }
        }
    }
    int main() {
        int data[] = {3, 2, 23, 4, 65, 12, 6};
        size_t size = sizeof(data) / sizeof(data[0]);
        selectSort(data, size);
        for(size_t i = 0; i < size; ++i)
            cout << data[i] << ' ';
        cout << endl;
    }
    
    选择排序特点:
    本例题的平均时间复杂度:O(n^2); 和冒泡一样;
    循环次数和插入排序一样;
    稳定性:稳定,和冒泡一样;
    敏感性:不敏感,无论数据是否有序,都得寻找最小值;

6.    快速排序(分组排序)
    步骤:
    1)任选一个数(一般选择中间位置)作为基准;
    2)重排序列,比基准小的放左边,比基准大的放右边,和基准相等的任意,这个过程叫做分组;
    3)以递归的方式,分别对小于基准的分组和大于基准的分组分别进行排序;
    
    void quickSort(int data[], size_t left, size_ right) {
        size_t p = (left + right) / 2;                // 找基准值
        int pivot = data[p];                            // 备份基准值
        for(size_t i = left, j = right; i < j;) {    // 只要 i 和 j 不相碰,循环继续;
            while(! (i >= p || pivot < data[i]))
                ++i;
            if(i < p) {                                 // 第二种情况
                data[p] = data[i];
                p = i;
            }
            while(! (j <= p || data[j] < pivot))
                ++j;
            if(j > p) {
                data[p] = data[j];
                p = j;
            }
        }
        data[p] = pivot;
        if(p - left > 1)
            quickSort(data, left, p - 1);
        if(right - p > 1)
            quickSort(data, p + 1, right);
    }
    int main() {
        int data[] = {3, 2, 23, 4, 65, 12, 6};
        size_t size = sizeof(data) / sizeof(data[0]);
        quickSort(data, 0, size -1);
        for(size_t i = 0; i < size; ++i)
            cout << data[i] << ' ';
        cout << endl;
    }

    快速排序特点:
    平均时间复杂度为:O(NlogN), 也是抛物线,但是增长缓慢;
    稳定性:不稳定;
    如果每次分组都是均匀分组,那么速度最快;

7.    自己写 my_qsort() 函数,实现 qsort() 排序功能:
    #include <cstring>
    #include <cstdlib>
    static void quickSort(void* base, size_t left, size_t right, size_t size,
                int (*compar)(const void*, const void*)) {
        size_t p = (left + right) / 2;
        void* pivot = malloc(size);
        memcpy(pivot, base + p * size, size);
        size_t i,j;
        for(i = left, j = right; i < j; ) {
            while(!(i >= p || cmpar(pivot, base + i * size) < 0))
                ++i;
            if(i < p) {
                memcpy(base + p * size, base + i * size, size);
                p = i;
            }
            while(!(j <= p || cmpar(base + j * size, pivot) < 0))
                --j;
            if(j > p) {
                memcpy(base + p * size, base + j * size, size);
                p = j;
            }
        }
        memcpy(base + p * size, pivot, size);
        free(pivot);
        if(p - left > 1)
            quickSort(base, left, p-1, size, compar);
        if(right - p > 1)
            quickSort(base, p + 1, right, size, compar);
    }
    
    void my_qsort(void* base, size_t numb, size_t size,
                int (*compar)(const void*, const void*)) {
        quickSort(base, 0, numb - 1, size, compar);        // 调用快速排序
    }
    // 常量排序
    int cmpInt(const void* a, const void* b) {
        return *(const int*)a - *(const int*)b;
    }
    // 字符串排序
    int cmpStr(const void* a, const void* b) {
        return strcmp(*(const char* const*)a, *(const char* const*)b);
    }
    // 把 data1,data2 合并到 data3,并保证有序;前提:data1 和 data2 都是有序的;
    // 思路:1 和 2 里的数据比大小,把小的放入 3,以此类推;
    void merge(int data1[], size_t size1, int data2[], size_t size2, int data3[]) {
        size_t i = 0, j = 0, k = 0;
        for(;;)                                        // 死循环;
            if(i < size1 && j < size2)                // 如果 data1 和 data2 里面都有数据;
                if(data1[i] <= data2[j])                // 如果 data1 小,放入 data3;
                    data3[k++] = data1[i++];
                else
                    data3[k++] = data2[j++];
            else if(i < size1)                        // 如果 data2 数据完了;
                data3[k++] = data1[i++];                // 把 data1 里剩余的数据直接赋值给 data3;
            else if(j < size2)
                data3[k++] = data2[j++];
            else
                break;
    }
    // l 到 m 之间有序,m 到 r 之间有序,合并后依然有序;
    void merge(int data[], size_t l, size_t m, size_t r) {
        int* res = new int[r-l+1];
        merge(data+l, m-l+1, data+m+1, r-m, res);
        for(size_t i = 0; i < r - l + 1; ++i)
            data[l+i] = res[i];
        delete[] res;
    }
    // 归并排序
    void mergeSort(int data[], size_t left, size_t right) {
        if(left < right) {
            int min = (left + right) / 2;
            mergeSort(data, left, mid);
            mergeSort(data, mid + 1, right);
            merge(data, left, mid, right);
        }
    }
    int main() {
        int* pa[] = {2, 3, 33, 6, 12};
        my_qsort(pa, 5, sizeof(pa[0]), cmpInt);
        char* ps[] = {"shanghai", "beijing", "xian"};
        my_qsort(ps, 3, sizeof(ps[0]), cmpStr);
        
        int data1[] = {22,33,45, 78};        // data1 是有序的;
        int data2[] = {23,55};                // data2 也是有序的;
        int data3[6];
        merge(data1, 4, data2, 2, data3);    // 把 data1,data2 合并到 data3,并保证有序;
        for(size_t i = 0; i < 6; ++i)
            cout << data3[i] << ' ';
        cout << endl;
        
        int data[] = {10, 6, 7, 8, 5, 6, 7, 2, 1};
        merge(data, 1, 3, 6);                // 1~3 有序,4~6 有序;
        for(size_t i = 0; i < 9; ++i)
            cout << data[i] << ' ';
        cout << endl;
        return 0;
    }

8.    归并排序
    步骤:
    1)申请空间,其大小为两个有序序列之和;
    2)设定两个指针,分别指向两个有序序列的起始位置;
    3)比较两个指针的目标,选这小值放入合并空间,并将指针移到下个位置;
    4)重复步骤 3,直到某一指针到达序列尾;
    5)将另一序列的剩余元素复制到合并空间;
    
    例题:把 data1[],data2[] 合并到 data3[],并保证有序;前提:data1 和 data2 都是有序的;
    思路:1 和 2 里的数据比大小,把小的放入 3,以此类推;
    // 异地排序
    void merge(int data1[], size_t size1, int data2[], size_t size2, int data3[]) {
        size_t i = 0, j = 0, k = 0;
        for(;;)                                        // 死循环;
            if(i < size1 && j < size2)                // 如果 data1 和 data2 里面都有数据;
                if(data1[i] <= data2[j])                // 如果 data1 小,放入 data3;
                    data3[k++] = data1[i++];
                else
                    data3[k++] = data2[j++];
            else if(i < size1)                        // 如果 data2 数据完了;
                data3[k++] = data1[i++];                // 把 data1 里剩余的数据直接赋值给 data3;
            else if(j < size2)
                data3[k++] = data2[j++];
            else
                break;
    }
    // 本地排序
    // l 到 m 之间有序,m 到 r 之间有序,合并后依然有序;
    void merge(int data[], size_t l, size_t m, size_t r) {
        int* res = new int[r-l+1];
        merge(data+l, m-l+1, data+m+1, r-m, res);
        for(size_t i = 0; i < r - l + 1; ++i)
            data[l+i] = res[i];
        delete[] res;
    }
    // 归并排序
    void mergeSort(int data[], size_t left, size_t right) {
        if(left < right) {
            int min = (left + right) / 2;
            mergeSort(data, left, mid);
            mergeSort(data, mid + 1, right);
            merge(data, left, mid, right);
        }
    }
    int main() {
        int data1[] = {22,33,45, 78};        // data1 是有序的;
        int data2[] = {23,55};                // data2 也是有序的;
        int data3[6];
        merge(data1, 4, data2, 2, data3);    // 把 data1,data2 合并到 data3,并保证有序;
        for(size_t i = 0; i < 6; ++i)
            cout << data3[i] << ' ';
        cout << endl;
        
        int data[] = {10, 6, 7, 8, 5, 6, 7, 2, 1};
        merge(data, 1, 3, 6);                // 1~3 有序,4~6 有序;
        for(size_t i = 0; i < 9; ++i)
            cout << data[i] << ' ';
        cout << endl;
        return 0;
    }
    
    递归排序特点:
    平均时间复杂度:O(2NlogN);
    稳定性:稳定;
    数据敏感性:不敏感;
    非就地排序,需要分配空间,不适合对大数据量排序;

9.    线性查找
    从表头开始,依次比较,直到找到与查找目标匹配的元素;或者找不到;
    
    int lineFind(int data[], int size, int key) {
        for(int i = 0; i < size; ++i)
            if(data[i] == key)
                return i;
        return -1;
    }
    int main() {
        int data[] = {3, 5, 5, 2, 8};
        size_t size = sizeof(data) / sizeof(data[0]);
        size_t i = lineFind(data, size, 2);
        if(i == -1)
            cout << "can't find" << endl;
        else
            cout << data[i] << endl;
        return 0;
    }
    
    线性查找特点:
    平均时间复杂度:O(N);
    数据敏感性:对数据有序性无任何要求;

10.    二分查找
    前提,序列需要是有序的;
    将表中间位置的值与查找目标比较,
    如果比中值小,放左边;反之放右边;如果相等,则查找成功;
    
    int binFind(int data[], int size, int key) {
        int left = 0;
        int right = size -1;
        while(left <= right) {
            int mid = (left + right) /2;
            if(data[mid] < key)            // 如果 key 大于中间值,到右边查找;
                left = mid + 1;
            else if(key > data[min])        // 如果 key 小于中间值,到左边查找;
                right = mid - 1;
            else
                return mid;
        }
        return -1;        // 查找失败;
    }
    int main() {
        int data[] = {3, 5, 5, 6, 8};
        size_t size = sizeof(data) / sizeof(data[0]);
        size_t i = binFind(data, size, 6);
        if(i == -1)
            cout << "can't find" << endl;
        else
            cout << data[i] << endl;
        return 0;
    }
    
    二分查找特点:
    平均时间复杂度:O(logN); 快!
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值