Author Archives: fangfang12138

Qt——QThread

事件驱动与事件循环

1、QT是事件驱动。在建立工程时,main.cpp如下部分代码,QAplication::exec()即启动了事件循环,每一个信号被当做事件来排队处理。

    QApplication a(argc, argv);
    /*...*/
    return a.exec();

2、模态对话框和事件循环参考这里
1)QWidget有非模态和模态两种展示方式
即QWidget::show() & QWidget::exec()。
2)实际上,QWidget::exec() 就是 setAttribute+show()+eventloop
3)QDialog继承自QWidget,对于模态对话框,会启用自己的事件循环。此时,主线程的事件循环阻塞,等待模态对话框的事件循环结束后,解除阻塞。
4)模态对话框的事件循环不会在单独的子线程执行,因此,事件循环跟线程没有必然联系。

QThread

1、QThread子类成员函数的运行线程
继承自QThread的类,在使用过程中,对于不在run()函数内执行的成员函数,比如一些槽函数,一般是在主线程运行(因为QThread子类本身一般是在主线程)。如何使其在次线程执行?
首先,明白一下几点
1)QThread负责管理线程,管理的线程(子线程)和依附的线程不同。
2)QThread::run()如同main(),运行run则开启线程、结束run则结束线程。
3)对于信号与槽,connect函数强调发送信号的线程和接受者依附的线程。
connect函数的最后一个参数:队列连接(槽函数在接受者依附的线程执行)、直接连接(槽函数在信号发射的线程执行)…

然后,想要成员函数在次线程运行
1)正常创建QThread对象。
2)把需要的槽或者函数写成一个类(就叫Class吧),继承自QObject。
3)把这个Class类的实例化对象转移到次线程。

class Class: public QObject

QThread t
Class c
c.moveToThread(&t)

2、跨线程指针的new和delete
对于上述实例化的对象(上述的c)中,如果需要给指针new一个空间,不能在构造函数中new,同样也不能在析构函数中delete。
因为指针的使用是在次线程,而构造和析构是在主线程完成,当关闭程序时,会报错(Cannot send events to objects owned by a different thread)。
解决方法是将指针的new和delete放到类(上述的Class)的槽函数中,通过主线程发送信号实现构造和释放。看这里。
要注意的是要确保delete在线程quit之前,如果这两个动作一前一后执行,比如下面这样,建议connect方式用Qt::BlockingQueuedConnection

    emit toDelete();
    t.quit();

3、跨线程信号与槽问题1——次线程的槽不执行
上面的解决方法会涉及到跨线程的信号和槽,一般我们在connect时可以通过Qt::AutoConnection或者Qt::QueuedConnection的方式实现跨线程的连接。
理论上,这就足够了,但实际上,尽管使用Qt::QueuedConnection,仍可能出现主程序的信号无法触发从线程中QObject子类对象的槽。
原因参考这里。
1)只有当从线程的消息循环正在运行的时候,才能正常接收信号或者说事件。
2)QThread的exec会开消息循环,调用start接口默认会执行exec,因此也会开消息循环。
3)start启线程后是否执行exec取决于run接口的实现,默认是调exec;如果重写了run接口,修改了默认行为,不再执行exec,也就不会运行消息循环。

4、跨线程信号与槽问题2——如何在次线程connect
把主线程的对象的指针通过信号发送到次线程。

注意!!!次线程内部的信号槽的连接建议用Qt::DirectConnection。

参考

1、https://blog.csdn.net/lynfam/article/details/7081757
2、https://blog.csdn.net/dbzhang800/article/details/6300416
3、https://www.cnblogs.com/judes/p/12943716.html
4、https://blog.csdn.net/u011388696/article/details/107854759

Qt——信号与槽的原理

QT元对象系统

QT元对象系统的三个重要组成包括:
1)QObject类(所有使用元对象系统的基类)
2)Q_OBJECT(所在类可以使用元对象特性)
3)MOC(meta-object-compiler,元对象编译器,预处理包含Q_OBJECT宏的类)

整体的编译过程

首先进行MOC预处理,Q_OBJECT宏将展开和自动生成一些变量定义、函数声明等代码,同时生成moc_xxx.cpp,之后进行普通编译。

信号与槽的预处理:看成普通函数

1)在MOC预处理过程中,Q_OBJECT将展开,类中定义的信号和槽也被处理。
2)处理后会将信号与槽都作为可回调的函数,根据id调用,函数及其id的定义在Q_OBJECT展开的函数qt_static_metacall中,如实例1
3)其区别是在不同的关键字(signals,slots)声明下,信号的实现由moc完成,如实例2,而槽函数的实现需要自己完成。

信号与槽的关联:构建Connection对象

1)connect函数构造了一个Connection对象,将接发对象、函数(信号槽)的id、连接方式等保存在发送者内存,如实例3。
2)而发送者将内存中的Connection对象保存在QObjectConnectionListVector中,QObjectConnectionListVector是个数组,通过信号id索引。
3)一个信号可以连接多个槽,则信号id索引的内容是数组某个位置的ConnectionList。

信号的触发与槽的执行:执行函数回调

1)执行emit,则相当于调用QMetaObject::activate函数,此时通过信号id,获得ConnectionList。
2)对于直连的信号槽,会直接执行槽函数id对应的函数;
3)对于队列连接的槽函数,将通过QMetaObject::queued_activate,抛出QMetaCallEvent事件,把Connection扔到接受者所在线程,在接受者所在线程的事件循环中执行,如实例4。

参考

1、https://www.cnblogs.com/swarmbees/p/10816139.html

实例

实例1

void MasterConsole::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MasterConsole *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->publishOrder((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
        case 1: _t->toBuilder(); break;
        case 2: _t->toDeleter(); break;
        case 3: _t->on_btnTestStartDistribution_clicked(); break;
        default: ;
        }
//...
}

实例2

// SIGNAL 1
void MasterConsole::toBuilder()
{
    QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}

实例3

QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;                              //发送者
c->signal_index = signal_index;             //信号索引
c->receiver = r;                            //接收者
c->method_relative = method_index;          //槽函数索引
c->method_offset = method_offset;           //槽函数偏移 主要是区别于多个信号
c->connectionType = type;                   //连接类型
c->isSlotObject = false;                    //是否是槽对象 默认是true
c->argumentTypes.store(types);              //参数类型
c->nextConnectionList = 0;                  //指向下个连接对象
c->callFunction = callFunction;             //静态回调函数,也就是qt_static_metacall

QObjectPrivate::get(s)->addConnection(signal_index, c.data());

实例4

void QMetaObject::activate(QObject *sender, int signalOffset ......)
{
    . . . . . .
    //发送方和接收方是否在同一个线程中
        const bool receiverInSameThread =  currentThreadId == receiver->d_func()->threadData->threadId;
         //决定连接立刻发送还是加入事件队列
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection))
                {
                queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
                continue;
            } else if
//...
}

static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv,  QMutexLocker &locker)
{
  ...
     QMetaCallEvent *ev = c->isSlotObject ?
     new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
     new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, 
                        sender, signal, nargs, types, args);

    //post事件到接收者所属线程的QThreadData::postEventList队列中
     QCoreApplication::postEvent(c->receiver, ev);
  ...
}

实例5:一个简单的moc_xxx.cpp文件

/****************************************************************************
** Meta object code from reading C++ file 'masterconsole.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.6)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../SandTable_MasterConsole/masterconsole.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'masterconsole.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.6. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MasterConsole_t {
    QByteArrayData data[12];
    char stringdata0[235];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_MasterConsole_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_MasterConsole_t qt_meta_stringdata_MasterConsole = {
    {
QT_MOC_LITERAL(0, 0, 13), // "MasterConsole"
QT_MOC_LITERAL(1, 14, 12), // "publishOrder"
QT_MOC_LITERAL(2, 27, 0), // ""
QT_MOC_LITERAL(3, 28, 2), // "id"
QT_MOC_LITERAL(4, 31, 5), // "order"
QT_MOC_LITERAL(5, 37, 9), // "toBuilder"
QT_MOC_LITERAL(6, 47, 9), // "toDeleter"
QT_MOC_LITERAL(7, 57, 35), // "on_btnTestStartDistribution_c..."
QT_MOC_LITERAL(8, 93, 35), // "on_btnTestCloseDistribution_c..."
QT_MOC_LITERAL(9, 129, 33), // "on_btnTestStartManagement_cli..."
QT_MOC_LITERAL(10, 163, 33), // "on_btnTestCloseManagement_cli..."
QT_MOC_LITERAL(11, 197, 37) // "on_btnTestFunction_singleCar1..."

    },
    "MasterConsole\0publishOrder\0\0id\0order\0"
    "toBuilder\0toDeleter\0"
    "on_btnTestStartDistribution_clicked\0"
    "on_btnTestCloseDistribution_clicked\0"
    "on_btnTestStartManagement_clicked\0"
    "on_btnTestCloseManagement_clicked\0"
    "on_btnTestFunction_singleCar1_clicked"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_MasterConsole[] = {

 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       8,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       3,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    2,   54,    2, 0x06 /* Public */,
       5,    0,   59,    2, 0x06 /* Public */,
       6,    0,   60,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       7,    0,   61,    2, 0x08 /* Private */,
       8,    0,   62,    2, 0x08 /* Private */,
       9,    0,   63,    2, 0x08 /* Private */,
      10,    0,   64,    2, 0x08 /* Private */,
      11,    0,   65,    2, 0x08 /* Private */,

 // signals: parameters
    QMetaType::Void, QMetaType::Int, QMetaType::QString,    3,    4,
    QMetaType::Void,
    QMetaType::Void,

 // slots: parameters
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,

       0        // eod
};

void MasterConsole::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MasterConsole *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->publishOrder((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
        case 1: _t->toBuilder(); break;
        case 2: _t->toDeleter(); break;
        case 3: _t->on_btnTestStartDistribution_clicked(); break;
        case 4: _t->on_btnTestCloseDistribution_clicked(); break;
        case 5: _t->on_btnTestStartManagement_clicked(); break;
        case 6: _t->on_btnTestCloseManagement_clicked(); break;
        case 7: _t->on_btnTestFunction_singleCar1_clicked(); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (MasterConsole::*)(int , QString );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::publishOrder)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (MasterConsole::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::toBuilder)) {
                *result = 1;
                return;
            }
        }
        {
            using _t = void (MasterConsole::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::toDeleter)) {
                *result = 2;
                return;
            }
        }
    }
}

QT_INIT_METAOBJECT const QMetaObject MasterConsole::staticMetaObject = { {
    &QMainWindow::staticMetaObject,
    qt_meta_stringdata_MasterConsole.data,
    qt_meta_data_MasterConsole,
    qt_static_metacall,
    nullptr,
    nullptr
} };

const QMetaObject *MasterConsole::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

void *MasterConsole::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_MasterConsole.stringdata0))
        return static_cast<void*>(this);
    return QMainWindow::qt_metacast(_clname);
}

int MasterConsole::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QMainWindow::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 8)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 8;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 8)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 8;
    }
    return _id;
}

// SIGNAL 0
void MasterConsole::publishOrder(int _t1, QString _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
void MasterConsole::toBuilder()
{
    QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}

// SIGNAL 2
void MasterConsole::toDeleter()
{
    QMetaObject::activate(this, &staticMetaObject, 2, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

Qt——win10下程序打包发布

写这篇博客是因为终于成功打包出去一个程序,之前放到其他设备上总是缺这少那…

ok,开始记录….

Step 1 release exe文件

这一步比较简单,把编译选项从debug模式改成release模式,然后,重新编译运行一遍。会在编译文件夹下的release文件夹内生成那个exe文件。

Step 2 使用windeployqt.exe打包到文件夹

  1. 将第一步生成的exe文件,新建个文件夹,把它放进去,文件夹的位置似乎没有特别的要求,就像这样↓
    在这里插入图片描述

  2. 打开终端(Windows powershell,就用管理员那个),然后把目录定位到刚刚建立的文件夹
    在这里插入图片描述
  3. 运行windeployqt.exe进行打包,这里要知道自己的windeployqt.exe在哪,一般在自己的Qt安装目录
    在这里插入图片描述
  4. 执行,然后在那个新建的文件夹里就多了一堆文件和夹
    在这里插入图片描述
  5. 这种事情怎么能没有一键操作呢,编写一个bat文件,双击执行即可(bat文件似乎不能编辑,可以先建立一个txt文件,然后修改文件类型就ok)(喔,好像也没有很一键)
    cmd /k "cd /d [windeployqt路径] && windeployqt.exe [自己exe路径]\xxx.exe

    cmd /k "cd /d D:\Qt5.12.6\5.12.6\msvc2017_64\bin\ && windeployqt.exe C:\Users\25834\Desktop\blog_pkg\xxx.exe

    Step 3 打包成一个文件

  6. 下载打包工具 Enigma Virtual Box
    在这里插入图片描述

  7. 选择自己的exe文件,并把整个文件添加到目录树,移除目录树中的exe
    在这里插入图片描述
    在这里插入图片描述

  8. 可以选择是否压缩,最后执行封包,结束后,可以运行看看
    在这里插入图片描述
    在这里插入图片描述
    (选择压缩,则生成的程序启动的时候要先解包,因此时间慢,不压缩,程序就会比较大,可以权衡。)

(我也加一个,亲测可用…)

参考

  1. 【win】【qt5打包】【qt程序打包成一个可执行文件(带图标任何win都可以运行哦)】

C++_8——开源串口库

For Linux

CppLinuxSerial

一个简单好用的serial库 https://github.com/gbmhunter/CppLinuxSerial

  1. 下载、编译、安装如下
    git clone https://github.com/gbmhunter/CppLinuxSerial.git
    cd CppLinuxSerial/ && mkdir build && cd build 
    cmake ..
    make
    sudo make install
  2. 安装位置如下,生成了一个静态库,头文件也只有俩
    Installing: /usr/local/lib/libCppLinuxSerial.a
    Installing: /usr/local/include/CppLinuxSerial
    Installing: /usr/local/include/CppLinuxSerial/SerialPort.hpp
    Installing: /usr/local/include/CppLinuxSerial/Exception.hpp
  3. 测试

    // serial库的头文件,需要在CmakeLists.txt里面添加库,或这在编译时添加库
    #include <CppLinuxSerial/SerialPort.hpp>
    using namespace mn::CppLinuxSerial;
    int main() {
    // 创建对象
    // 自定义波特率 SerialPort serialPort("/dev/ttyACM0", 13000);
    SerialPort serialPort("/dev/ttyUSB0", BaudRate::B_57600);
    
    // 接收超时时间
    // 阻塞接收:        输入为0,代表非阻塞; 输入为-1代表阻塞接收
    serialPort.SetTimeout(-1); // Block when reading until any data is received
    
    // 打开
    serialPort.Open();
    
    // 发送
    serialPort.Write("Hello");
    
    // 接收
    std::string readData;
    serialPort.Read(readData);
    
    // 关闭
    serialPort.Close();
    }

C++_11——设计模式_单例模式

(是C++专栏第11篇,非C++11标准…)

单例模式

【C++】 ——C++单例模式中的饿汉和懒汉模式

  1. 每个类仅有一个实例,实例被所有程序模块共享
  2. 饿汉模式下,在定义时实例化,在使用之前造成资源浪费
  3. 懒汉模式下,在使用时实例化,需要处理线程安全问题
  4. 应用如,线程池、对话框、日志文件、硬件驱动、注册表…
  5. 注意指针的释放

线程安全的懒汉单例模式(加锁)↓↓↓

// SingeltonModel.h文件
#include <mutex>
#pragma once

// 线程安全类
class SingeltonThread
{
private:
    SingeltonThread() {}
    static std::shared_ptr<SingeltonThread> m_instance;
    static std::mutex m_mutex;
public:
    static SingeltonThread * getInstance()
    {
        if (nullptr == m_instance)
        {
            m_mutex.lock();
            if (nullptr == m_instance)
                m_instance = std::make_shared<SingeltonThread>();
            m_mutex.unlock();
        }
        return m_instance;
    }
};
std::shared_ptr<SingeltonThread> SingeltonThread::m_instance = nullptr;
std::mutex SingeltonThread::m_mutex;

注:

  1. #ifndef与#pragma once
  2. c++中static的作用

C++_10——定时器 & Linux下基于信号的进程间通信

  这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。
以C++11标准为基础。

参考https://blog.csdn.net/ljianhui/article/details/10128731

Linux下 定时器

这里Linux下定时器 基于Linux下进程间的通信(基于信号的通信)

    #include <signal.h>
    #include <unistd.h> //alarm的头文件

    // 定义定时触发函数
    void timer(int sig)
    {
        if(SIGALRM == sig)
            flag1 = 0;
    }

    // 绑定定时触发函数:改变定时触发信号的默认行为,改为执行timer函数,信号由alarm(int seconds)函数生成
    signal(SIGALRM, timer);

    // 设置下次触发时间
    alarm(10);

Linux下 基于信号的进程间通信

进程与线程

  1. 进程是系统进行任务调度和资源分配的最小单位,进程之间可以并发执行。
  2. 线程是程序执行流的最小单元,一个进程可以有多个线程,进程内的线程在其他进程不可见。
  3. 线程切换快于进程切换。
  4. 任务调度:时间片轮转,每个任务执行一个时间片的时间长度,时间片结束后,强制暂停,执行另一个时间片的程序,等到该任务的时间片再次到来。
  5. 进程间资源互不可见,需要通过内核缓冲区进行数据交换,进程A将数据从用户空间放到内核缓冲区,进程B将其从中取走。
  6. 进程间的通信方式主要包括:
    匿名管道
    有名管道
    消息队列
    信号
    信号量
    共享内存
    数据报套接字
    数据流套接字
  7. 线程同步的方式主要包括:基于信号量、互斥量等

基于信号的进程间通信

  1. 信号由某些进程的事件触发/产生,由某些进程捕获处理,可作为一种进程间通信的方式。
  2. 信号的种类
信号名 说明 备注
SIGALRM 超时警告:由alarm函数设置的定时器产生
SIGHUP 连接挂断:由一个处于非连接状态的终端发送给控制进程,或者由控制进程在自身结束时发送给每个前台进程 不懂
SIGKILL 终止进程:因为这个信号不能被捕获或忽略,所以一般在shell中用来强制终止异常进程
SIGINT 终端中断:一般由从终端输入的Ctrl+C组合键 或 预先设置好的中断字符产生
SIGPIPE 如果在向管道写数据时,没有与之对应的读进程,就会产生 TCP客户端向服务端发送消息,客户端关闭后,服务端返回消息时会收到内核发出的SIGPIPE信号
SIGTERM 终止:作为一个请求被发送,要求进程结束运行。UNIX在关机时用该信号要求系统服务停止运行,是kill命令默认发送的信号
SIGUSR1 用户定义:进程之间通信的一个方式,可定义
SIGUSR2 同上
  1. 捕获信号后的自定义处理1——signal函数
    自定义信号处理方式使用signal函数,输入为信号名和处理函数func。
    处理函数func也可以换成特殊值: SIG_IGN(忽略信号)、SIG_DFL(恢复信号的默认行为)。
    处理函数func输入是int,无返回值。输入的int是信号名的值,如 SIGALRM == 14。
    举个例子就是第一部分的定时器的使用。

    #include <signal.h>
    void (*signal(int sig, void (*func)(int)))(int);
  2. 捕获信号后的自定义处理2——sigaction函数
    与signal函数作用相同,可阻塞指定信号,待处理函数建立后处理(防止,处理函数未建立,而先接收到信号)

    struct sigaction act;
    act.sa_handler = func;
    //创建空的信号屏蔽字,即不屏蔽任何信息
    sigemptyset(&act.sa_mask);
    //使sigaction函数重置为默认行为
    act.sa_flags = SA_RESETHAND;
    
    sigaction(SIGINT, &func, 0);
  3. 触发信号

    // alarm函数
    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    // kill函数:给进程pid,发送信号sig
    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int sig);

    函数pause可以挂起进程。

  4. 当执行函数触发时,又一个优先级更高的信号到来,当这一高优先级信号处理结束后,前一个函数是否可以再次进入?部分函数可以:
    在这里插入图片描述

参考

  1. https://www.cnblogs.com/qianqiannian/p/7010909.html
  2. 进程间的通信方式——pipe(管道)
  3. https://blog.csdn.net/ljianhui/article/details/10287879

C++_9——文件读写&输入输出流

  这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。
以C++11标准为基础。

继承关系

parent c1 c2 c3 c4
ios_base ios istream iostream fstream
stringstream
ifstream
istringstream
ostream ofstream
ostringstream
streambuf filebuf
stringbuf

C++文件读写

文件读写的类

Narrow characters (char) Wide characters (wchar_t)
ifstream wifstream Input file stream class (class )
ofstream wofstream Output file stream (class )
fstream wfstream Input/output file stream class (class )
filebuf wfilebuf File stream buffer (class )
  1. ofstream, ifstreamfstream 是分别继承自ostream, istreamiostream ,因而像打开文件的模式、读写数据的方式等,都可以使用父类的成员和成员函数。
  2. ofstream, ifstreamfstream 内部维护一个filebuf对象,作为内部流缓冲区,对关联文件执行输入输出操作。这也对应着ostream, istreamiostream内部维护的streambuf对象。

开闭文件

ofstream, ifstreamfstream 基本一致,以ifstream为例

  1. 关联与关闭文件
    #include <fstream> 
    // 方法一:构造   
    ifstream(); 
    explicit ifstream (const char* filename, ios_base::openmode mode = ios_base::in);
    explicit ifstream (const string& filename, ios_base::openmode mode = ios_base::in);
    // e.g.
    std::ifstream ifs ("test.txt", std::ifstream::in);
    // 方法二:成员函数 open
    void open (const   char* filename,  ios_base::openmode mode = ios_base::in);
    void open (const string& filename,  ios_base::openmode mode = ios_base::in);
    // e.g.
    std::ifstream ifs;
    ifs.open ("test.txt", std::ifstream::in);
    // 关闭文件
    void close();
  2. 文件打开类型
    // 注:根据继承关系,此成员变量都能用
    std::ifstream::in   
    std::ios_base::in
类型 说明
in File open for reading,对于ifstream必有
out File open for writing,内部的filebuf也可设置为输出模式
binary 以二进制方式打开
app 从文件尾开始写
ate 从文件尾开始读
trunc 如果文件已存在,则先删除文件
  1. 检查打开情况
    bool is_open() const;

读写文件

  1. 读写文件

    // get()
    // single character
        int get();
        istream& get (char& c);
    // c-string
        istream& get (char* s, streamsize n);
        istream& get (char* s, streamsize n, char delim);
    // stream buffer
        istream& get (streambuf& sb);
        istream& get (streambuf& sb, char delim);
    // getline()
    istream& getline (char* s, streamsize n );
    istream& getline (char* s, streamsize n, char delim );
    // operator (>>) 不再赘述
    // read & write
    // Extracts n characters from the stream and stores them in the array pointed to by s.
        istream& read (char* s, streamsize n);          
    // Inserts the first n characters of the array pointed by s into the stream.
        ostream& write (const char* s, streamsize n);   
  2. 获取和设置流指针
    // 获取当前流指针位置
    streampos tellg();  // 读操作时,流指针当前位置,返回值是一个整数
    streampos tellp();  // 写操作时,流指针当前位置
    // 设置流指针位置
    istream& seekg (streampos pos);                         // 给定从文件开始的绝对位置
    istream& seekg (streamoff off, ios_base::seekdir way);  // 给定从way开始偏移off的位置
    ostream& seekp (streampos pos);
    ostream& seekp (streamoff off, ios_base::seekdir way);
ios_base::seekdir取值 说明
ios_base::beg beginning of the stream
ios_base::cur current position in the stream
ios_base::end end of the stream
  1. 状态标志函数
    // Check whether eofbit is set  
    // 文件读到末尾时,eofbit会置位,对应返回true
    bool eof() const;   
    // Check whether badbit is set  
    // 如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
    bool bad() const;   
    // Check whether either failbit or badbit is set    
    // 除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
    bool fail() const;  
    // Check whether state of stream is good
    // 以上三个标志位(eofbit/badbit/failbit)任何一个为true,则goodbit置为0,对应返回false
    bool good() const;
读写状态标志位 意义 good()是否check eof()是否check fail()是否check bad()是否check rdstate()是否check
goodbit No errors (zero value iostate) true false false false goodbit
eofbit End-of-File reached on input operation false true false false eofbit
failbit Logical error on i/o operation false false true false failbit
badbit Read/writing error on i/o operation false false true true badbit

读写缓冲与同步

(下次一定… 实际上ifsream和ofstream都会维护一个filebuf对象,其一些成员函数内部都是执行的filebuf的成员函数)

下面是一段摘抄:

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。
当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:
当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
————————————————
版权声明:本文为CSDN博主「追求执着」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/kingstar158/article/details/6859379

C语言写入文件

#include “stdio.h"

int main()
{
    FILE *fp;
    fp=fopen("2.txt","w");      // 以写入的方式,新建或打开文件,并在文件末尾添加内容
    int n=1;
    fprintf(fp,"%d",n);
    fclose(fp);
    return 0;
}

备注

  1. explicit:显式单参数构造函数(有默认参数的多参也可以),目的是防止隐式类型转换带来的风险(如出现问题不方便排查等),同时对于单参构造函数建议使用explicit关键字。
    参考1参考2

参考

C++文件读写详解(ofstream,ifstream,fstream)

C++_7——网络通信(Linux)

  这一系列文章的目的是在学习了C++基础后,继续补充一些C++基础和进阶的知识点,包括C++11的相关内容。

1 TCP

client

//头文件
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// TCP配置
    //1、创建socket套接字
        // AF_INET(地址域:IPV4),AF_INET6(地址域:IPV6)
        // SOCK_STREAM(套接字类型 流式 TCP 套接字),SOCK_DGRAM:(套接字类型 数据报 UDP 套接字)
        // 0(协议类型 默认),返回-1表示创建失败
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
        return -1;

    //2、配置IP和端口
    struct sockaddr_in srv;                         //统一接口sockaddr_in 对应 IPV4
    srv.sin_family=AF_INET;
    srv.sin_port=htons(atoi("1122"));               //server的端口
    srv.sin_addr.s_addr=inet_addr("192.168.1.100"); //server的IP

    //3、连接
    socklen_t len=sizeof(struct sockaddr_in);
    if(connect(sockfd,(struct sockaddr*)&srv,len)<0)
        return -1;

// TCP收发
    //接收
    unsigned char buff_recv[1024];                  //定义接收的数据缓存
    memset(buff_recv, 0x00, sizeof(buff_recv));     //初始化为0
    recv(sockfd, buff_recv, sizeof(buff_recv)-1, 0);//阻塞接收 recv(sockfd, 数据缓存,接收数据长度,默认0)
    //发送
    unsigned char buff_send[2]={0x01,0x02};     //定义一个待发送的数据
    send(sockfd, buff_send, sizeof(buff_send), 0);  //发出 send(sockfd,数据,数据长度,默认0)

//关闭socket
    close(sockfd);

server

//头文件
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// TCP配置
    //1、创建socket套接字
        // AF_INET(地址域:IPV4),AF_INET6(地址域:IPV6)
        // SOCK_STREAM(套接字类型 流式 TCP 套接字),SOCK_DGRAM:(套接字类型 数据报 UDP 套接字)
        // 0(协议类型 默认),返回-1表示创建失败
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
        return -1;

    //2、配置本地IP和端口
    struct sockaddr_in server;                      //统一接口sockaddr_in 对应 IPV4
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi("1122"));                //server的端口(本机监听的端口)
    server.sin_addr.s_addr=inet_addr("192.168.1.100");  //server的IP(本机IP)

    //3、绑定本地IP和端口
    int bind_result = bind(sockfd, (struct sockaddr*)&server, sizeof(server));
    if (bind_result == -1)
    {
        close(sockfd);
        return -1;
    }

    //4、开始监听
    int listen_result = listen(sockfd,5); 
    if(listen_result == -1)
        return -1;

    //5、建立连接
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int  new_fd = accept(sockfd, (struct sockaddr*)&client, &len);

// TCP收发
    //接收
    unsigned char buff_recv[1024];              //定义接收的数据缓存
    memset(buff_recv, 0x00, sizeof(buff_recv)); //初始化为0
    recv(new_fd, buff_recv, sizeof(buff_recv)-1, 0);//阻塞接收 recv(sockfd, 数据缓存,接收数据长度,默认0)
    //发送
    unsigned char buff_send[2]={0x01,0x02}; //定义一个待发送的数据
    send(new_fd, buff_send, sizeof(buff_send), 0);  //发出 send(sockfd,数据,数据长度,默认0)

//关闭socket
    close(sockfd);

2 UDP

广播发送

//头文件
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// UDP配置
    //1、创建socket套接字
        // AF_INET(地址域:IPV4),AF_INET6(地址域:IPV6)
        // SOCK_STREAM(套接字类型 流式 TCP 套接字),SOCK_DGRAM:(套接字类型 数据报 UDP 套接字)
        // 0(协议类型 默认),返回-1表示创建失败
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(-1==sockfd)
            return -1;

    //2、配置socket为广播类型
        const int opt = 1;   
        int bro_result = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));  
        if(bro_result == -1)  
            return -1;  

    //3、配置广播地址和端口
        struct sockaddr_in addr_to;  
        socklen_t addr_to_len=sizeof(addr_to);
        memset(&addr_to, 0, addr_to_len);
        addr_to.sin_family=AF_INET;   
        addr_to.sin_port=htons(6000);  
        addr_to.sin_addr.s_addr=htonl(INADDR_BROADCAST);  

// UDP广播接收  
    // 定义数据缓存
        char buffer[] = {"abcdef"};
    //发送  
        int ret=sendto(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr_to, addr_to_len);   

//关闭socket
    close(sockfd);

广播接收

//头文件
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// UDP配置
    //1、创建socket套接字
        // AF_INET(地址域:IPV4),AF_INET6(地址域:IPV6)
        // SOCK_STREAM(套接字类型 流式 TCP 套接字),SOCK_DGRAM:(套接字类型 数据报 UDP 套接字)
        // 0(协议类型 默认),返回-1表示创建失败
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(-1==sockfd)
            return -1;

    //2、设置接收的IP和端口
        struct sockaddr_in addr_local;
        socklen_t addr_local_len=sizeof(addr_local);
        memset(&addr_local, 0, addr_local_len);
        addr_local.sin_family      = AF_INET;               // Use IPV4
        addr_local.sin_port        = htons(6000);           // 绑定端口          
        addr_local.sin_addr.s_addr = htonl(INADDR_ANY);     // 接收任意IP发来的数据

    //3、绑定上述设置到socket
        int bind_result = bind(sockfd, (struct sockaddr*)&addr_local, addr_local_len); 
        if (bind_result == -1)
        {
            close(sockfd);
            return -1;
        }

// UDP广播接收  
    // 定义数据缓存
        char buffer[30];
        memset(buffer, 0, 30);

        struct sockaddr_in addr_from;               // 用于保存消息来源的地址,即数据由哪个IP广播的
        socklen_t addr_from_len=sizeof(addr_from);
   //接收     
        int len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr_from, &addr_from_len);

//关闭socket
    close(sockfd);

3 非阻塞接收

#include <fcntl.h>  //接收数据阻塞与否,默认为阻塞模式

//配置为非阻塞模式(不用ioctl)
    int flag_fcntl = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flag_fcntl|O_NONBLOCK);

4 recv recvfrom read send sendto write

ssize_t write(int fd, const void*buf, size_t nbytes); //ssize_t 即有符号的size_t
ssize_t read(int fd, void *buf, size_t nbyte);

int recv(int sockfd, void *buf, int len, int flags);
int send(int sockfd, void *buf, int len, int flags);

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
int sendto(int sockfd, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
  1. 参考:Linux下,write/read,recv/send, recvfrom/sendto的区别
  2. read/wirte是通用的文件描述符操作;recv/send 通常应用于TCP;recvfrom/sendto通常应用于UDP。
  3. recv/send函数的最后一个参数为0时,功能上与read和write基本一致。
  4. read/wirte:从文件描述符fd中直接读写数据。
  5. recv/send:增加第四个参数控制读写操作。
  6. recvfrom/sendto:接收数据时获取对方IP,发送数据时指明对方IP。

面试1——剑指Offer笔记(C++)

源码

1 基础知识

1.1 编程语言

四个类型转换关键字

参考
1) static_cast
底层无const的一般静态转换,基本类型 / 具有继承关系的类型

//基本类型转换
int i = 0;
double d = static_cast<double>(i);  //相当于double d = double(i);
//转换继承类的对象为基类对象
class Base{};
class Derived: public Base{};
Derived d;
Base b = static_cast<Base>(d);  //相当于Base(d)

2) dynamic_cast
动态转换,派生类指针(引用)转换为基类指针(引用) / 没有继承关系但被转换类有虚函数

class Base{};
class Derived: public Base{};
//派生类指针转换为基类指针
Derived *pd = new Derived;
Base *pb = dynamic_cast<Base>(pd);
//检测
if(!pb){
  std::cout << "Failed Cast!";
}
//没有继承关系,但被转换类有虚函数
class A{
    virtual ~A();
};
class B{};
A *pa = new A;
B *pb = dynamic_cast<B>(pa);

3) const_cast
只改变const(常量)属性,不改变类型

const int *pci = 0;
//将常量转换为非常量
int *pk = const_cast<int*>(pci);  //相当于int *pk = int*(pci);
const A* pca = new A;
A *pa = const_cast<A*>(pca); //相当于A *pa = A*(pca);

4) reinterpret
重新解释、指针类型转换

//基本类型指针的类型转换
double d = 9.2;
double *pd = &d;
int *pi = reinterpret_cast<int*>(pd); //相当于int *pi = (int*)pd;
//不相关的类的指针类型转换
class A{};
class B{};
A *pa = new A;
B *pb = reinterpret_cast<B*>(pa); //相当于B *pb = (B*)pa;
//指针转换为整数
int *pi = 0;
long L = reinterpret_cast<long>(pi);  //相当于long L = (long)pi;

sizeof

1) 空类型使用sizeof,计算该类型实例的大小,使用实例,有大小,大小由编译器决定,一般是1
2) 有构造和析构函数的类型,一般也是1,实例调用这俩函数只需知道函数地址,函数地址与类型有关,与实例无关
3) 有虚函数的类型,大小为一个指针的大小(32/64->4/8),编译器会在实例中添加指向虚函数表的指针

复制构造函数

1) C++标准不允许复制构造函数进行值传递(递归溢出),允许常量引用

面试题1 赋值运算符函数

1) 返回引用(连续赋值),传入常量引用(高效、保护),释放已有内存(泄露),判断同一个实例
2) 异常安全
先释放内存还是先申请内存:内存不足的情况下,要保证原数据不变(不被提前释放),创建临时实例,构造函数new,new出来就交换,new不出来就抛出异常bad_alloc

1.2 数据结构

数组的效率

1) 静态数组:高时间效率,低空间效率
2) 动态数组:扩容操作增加时间复杂度(STL,vector)
3) 数组作为函数参数传递时,数组名将退化成同类型的指针

面试题3 数组中的重复数字

1) 题1找出重复数字,思路:把数字放到对应的下标的位置上,当发现对应位置已有该数字,则找到重复数字,对应空间O(1),时间O(n)
2) 题2不修改数组找出重复数字,思路:二分法,种类二分,确定每一段内的种类出现的次数,是否为该段的种类数。
3) 明确要求时间还是要求空间效率

面试题4 二维数组查找

1) 规律,缩小查找范围

常量字符串的地址

1) 常量字符串初始化字符数组,不同数组的地址不同,原因是不同字符数组已分配了不同位置的内存以拷贝常量字符串 P49
2) 常量字符串初始化字符指针,不同指针指向的地址相同,原因是无需重新分配内存,直接指向常量在内存中的地址

面试题5 替换空格

1) 一次移动到位,O(n),(此题不用自己扩容)
2) 考虑内存覆盖
3) 从尾部到头部,依次填入排序值

链表

1) 链表是考察重点
2) 为什么是传入指向指针的指针
要修改的是指针的内容(pHead,也就是修改一个地址),而不是修改指针指向的内容,因此传入的是指向指针的指针。
类比于,传入整型 int a 与传入其指针 int
a的关系,这里是传入指针 ListNode *pHead 和指向指针的指针 ListNode **pHead。

面试题6 从尾到头打印链表

1) 栈
2) 递归 链表过长,可能导致函数调用栈溢出

void PrintListReversingly_Recursively(ListNode* pHead)
{
    if(pHead != nullptr)
    {
        if (pHead->m_pNext != nullptr)
        {
            PrintListReversingly_Recursively(pHead->m_pNext);
        }

        printf("%d\t", pHead->m_nValue);
    }
}

1) 二叉树特例
二叉搜索树

红黑树

面试题7 重建二叉树

1) 已知前序和中序、或者已知后序和中序,都可以递归的形式,通过不断地 划分左右子树然后分别重建这里方法更简洁

面试题8 二叉树的下一个结点

1) 中序遍历:通过画图,分别考虑
2) case 1:存在右子树:下一个是其右子树的最左子结点(E -> K)
3) case 2:不存在右子树:
4) case 2.1:该结点是其父节点的左子结点,则下一个是其父节点(H -> D)
5) case 2.2:该结点是其父节点的右子结点,则下一个是向上溯源,直到溯源结点是其父节点的左子结点,则下一个是溯源结点的父节点(K -> A)
在这里插入图片描述

栈和队列

面试题9 用两个栈实现队列

1) 用两个队列实现栈

template <class T>
class CStack
{
    public: 
        CStack(void);
        ~CStack(void);
        void appendHead(const T& node);
        T deleteHead();

    private:
        queue<T> queue1;
        queue<T> queue2;
};

//入栈和出栈时,至少有一个队列为空
template <class T>
void CStack<T>::appendHead(const T& node)
{
    if(0 == queue1.size())      //向非空的队列push
        queue2.push(node);
    else if(0 == queue2.size())
        queue1.push(node);  
}

template <class T>
T CStack<T>::deleteHead()
{
    if(0 == queue1.size() && 0 == queue2.size())
        throw new exception("stack is empty");

    if(0 == queue1.size())      //从非空队列转移n-1个数据到空队列
    {
        while(1 != queue2.size())
        {
            T& data = queue2.top();
            queue2.pop();
            queue1.push(data);
        }
        T head = queue2.top();
        queue2.pop();
        return head;
    }
    else
    {
        while(1 != queue1.size())
        {
            T& data = queue1.top();
            queue1.pop();
            queue2.push(data);
        }
        T head = queue1.top();
        queue1.pop();
        return head;
    }
}

1.3 算法与数据操作

递归和循环

1) 递归优先
2) 递归,调用栈溢出,子问题重复计算
3) 自上而下的递归,可能出现子问题重复计算的问题,转而采用自下而上的循环

面试题10 斐波那契数列

1) 小青蛙,2^(n-1)
2) 覆盖矩形:f(1)=2,f(0)=0 ?

查找和排序

1) 查找重点:顺序查找、二分查找、哈希表查找、二叉排序树(二叉搜索树)
2) 排序各算法比较

排序 插入排序 冒泡排序 归并排序 快速排序
基本思想 自第二个记录开始,有序前插 相邻元素比较,逆序交换,逐遍把最大值移至最后 相邻两个/多个顺序表合并,初始每个元素都是一个顺序表 两侧依次扫描,将轴值移至正确位置
时间复杂度 n~n^2 n~n^2 nlbn nlbn~n^2
平均时间复杂度 n^2 n^2 nlbn nlbn
空间复杂度 1 1 n lbn~n
应用/优化 适用于元素数量少,且基本有序,改进:分割,分别直插,基本有序后全体直插 双向起泡 相邻-一趟-递归 递归次数-空间复杂度

3) 二分查找

bool BinarySearch(const int r[], const int length, const int searchData)
{
    int start = 0;
    int end = length-1;
    int mid = (start+end)/2;

    while(start<=end)
    {
        if(r[mid]==searchData)
            return true;
        else if(r[mid]>searchData)
            end = mid - 1;
        else
            start = mid + 1;

        mid = (start+end)/2;
    }

    return false;
}

4) 快速排序

//进行一次排序
int Partition(int r[], int length, int start, int end)
{
    if((r==nullptr) || (length<0) || (start<0) || (end>=length))
        throw new std::exception("invalid parameter.");

    int pivot = r[start];   //取第一个值为轴值,挖一个坑
    int i = start;          //左侧扫描指针
    int j = end;            //右侧扫描指针

    while(i<j)
    {
        //右侧扫描:将右侧一个大值移到左侧坑位,空出一个右侧坑位
        while(i<j && pivot<r[j])
            j--;

        if(i<j)
        {
            r[i] = r[j];
            i++;
        }

        //左侧扫描:将左侧一个小值移到右侧坑位,空出一个左侧坑位
        while(i<j && pivot>r[i])
            i++;

        if(i<j)
        {
            r[j] = r[i];
            j--;
        }
    }

    r[i] = pivot;   //把坑位补齐
    return i;
}

void QuickSort(int r[], int length, int start, int end)
{
    if((r==nullptr) || (length<0) || (start<0) || (end>=length))
        throw new std::exception("invalid parameter.");

    if(start < end)
    {
        int index = Partition(r, length, start, end);
        if(start < index) 
            QuickSort(r, length, start, index-1);
        if(index < end)
            QuickSort(r, length, index+1, end);
    }
}

面试题11 旋转数组的最小数字

1) 二分,旋转0个,顺序查找,特例

回溯法

面试题12 矩阵中的路径

#include <cstring>
void* memset( void* dest, int ch, std::size_t count );
/*
Parameters
dest    -   pointer to the object to fill
ch  -   fill byte
count   -   number of bytes to fill
*/

1) 起始位置,for/for
2) 结束条件,‘\0’字符转结尾
3) 约束条件,不能重复走visited
4) 递归,周围四个格子
5) 回溯,pathLength–

面试题13 机器人的运动范围

动态规划与贪婪算法

1) 动态规划:
最优解问题
可分解成子问题,子问题的最优解组合起来得到整个问题的最优解
可能存在重复的子问题
自上而下分析,自下而上求解
2) 贪婪算法:数学证明

面试题14 剪绳子

位运算

1) 移位操作https://blog.csdn.net/weixin_42315600/article/details/80567944
2) 无符号数,左移右添0,右移左添0
有符号数,左移右添0,右移左补符号位
3) 移位效率高于乘除

面试题15 二进制中1的个数

1) 整数减1,再与原数与运算

2 代码质量

2.1 完整性

代码完整性

1) 测试用例
功能测试/边界测试/负面测试
2) 错误处理:返回值、全局变量、C++异常处理

/*使用异常*/
#include <iostream>
using namespace std;
int main()
{
    double m, n;
    cin >> m >> n;
    try 
    {
        cout << "before dividing." << endl;
        if (n == 0)
            throw - 1;  //抛出整型异常
        else if (m == 0)
            throw - 1.0;  //拋出 double 型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch (double d) 
    {
        cout << "catch (double)" << d << endl;
    }
    catch (...) //捕获任何异常
    {
        cout << "catch (...)" << endl;
    }
    cout << "finished" << endl;
    return 0;
}

/*异常再抛出:
1、不处理
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);
如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。
2、处理,但继续抛出,如下*/
catch (string s ) 
{
     cout << "CountTax error : " << s << endl;
     throw; //继续抛出捕获的异常,上级调用函数再次去捕获
}

/*C++标准异常类
exception 类的派生类:bad_typeid、bad_cast、bad_alloc、ios_base::failure、out_of_range...
C++ 程序在碰到某些异常时,即使程序中没有写 throw 语句,也会自动拋出上述异常类的对象。
这些异常类还都有名为 what 的成员函数,返回字符串形式的异常描述信息。*/
#include <exception> //exception类,异常类的基类
#include <stdexcept> //logic_error和runtime_error两个公派自exception的类
#include <new>  //bad_alloc公派自exception的类

面试题16 数值的整数次方

1) 考虑:底数为0,指数为负
2) 递归乘方
3) 位运算效率

面试题17 打印从1到最大的n位数

1) 大数问题

面试2——题

1、动态规划

DP问题三步走

  1. 定义一个动态规划数组,明确数组元素,目的是保存计算的历史记录,节省重复计算时间
  2. 确定数组间的关系
  3. 找出初始值

最大连续子序列和

有一个数组,如1, -5, 8, 3, -4, 15, -8,查找其中连续和最大的相邻串的值。
在本例中,最大值为8 + 3 + -4 + 15 = 22。
  1. 定义动态规划数组元素:dp[i]表示到第i个元素的最大连续子序列和。
  2. 数组元素间的关系:dp[i] = max{ dp[i-1] + arc[i] , arc[i] },即,到第i个元素的最大连续子序列和,要么是继续累加,要么就是元素本身(重新开始一轮累加)。最终的最大值是dp数组中的最大值。(可以累加一个负数,但不能在负数上累加)
  3. 初始值:dp[0] = arc[0] 。

    int max_successive_sub_serial_sum(int* numbers, int size)
    {
    //定义与初始化
    int* dp = new int[size];
    dp[0] = numbers[0];
    int max_sum = dp[0];
    //推算
    for(int i=1;i<size;i++)
    {
        dp[i] = std::max( dp[i-1] + numbers[i] , numbers[i] );
        max_sum = dp[i]>max_sum ? dp[i] : max_sum;
    }
    
    delete[] dp;
    return max_sum;
    }
  4. 优化空间复杂度:只维护前后两个序列和

    int max_successive_sub_serial_sum_space(int* numbers, int size)
    {   
    //定义与初始化
    int last_sum = numbers[0];
    int max_sum = last_sum;
    //推算
    for(int i=1;i<size;i++)
    {
        int cur_sum = std::max( last_sum + numbers[i] , numbers[i] );
        max_sum = cur_sum>max_sum ? cur_sum : max_sum;
        last_sum = cur_sum;
    }
    
    return max_sum;
    }

二维网格与路径

一个 m x n 的二维网格,从左上角移动到右下角,一共多少种路径,每次只能向右或向下移动一格。
  1. 定义动态规划数组元素:dp[ i ][ j ] 是到达第 i 行第 j 列的路径总数,i 和 j 从0开始。
  2. 数组元素间的关系:dp[ i ][ j ] = dp[ i – 1 ][ j ] + dp[ i ][ j – 1 ],即这一点可以由上面网格和左侧网格抵达,路径总数为两路之和
  3. 初始值:dp[ 0 ][ 0 ] … dp[ 0 ][ n-1 ] = 1,dp[ 0 ] … dp[ 0 ][ m-1 ][ 0 ] = 1,最左侧和最上面的网格都只有一条路径。

    int unique_path(int rows, int cols)
    {
    //定义与初始化
    int** dp = new int*[rows];
    for(int i=0;i<rows;i++)
    {
        dp[i] = new int[cols];
        memset(dp[i],0,cols);   //memset是逐字节初始化,因此只有0和-1是正常初始化
    }       
    
    for(int i=0;i<cols;i++)
        dp[0][i]=1;
    
    for(int i=0;i<rows;i++)
        dp[i][0]=1;
    //推算
    for(int i=1;i<rows;i++)
        for(int j=1;j<cols;j++)
            dp[i][j] = dp[i-1][j] + dp[i][j-1];
    //输出            
    int sum = dp[rows-1][cols-1];
    
    for(int i=0;i<rows;i++)
        delete[] dp[i];
    delete[] dp;
    
    return sum;
    }
  4. 优化空间复杂度:只保存一行的记录,然后不断迭代

    int unique_path_space(int rows, int cols)
    {
    //定义与初始化
    int* dp = new int[cols];
    for(int i=0;i<cols;i++)
        dp[i] = 1;
    //推算
    for(int i=1;i<rows;i++)
        for(int j=1;j<cols;j++)
            dp[j] = dp[j] + dp[j-1];
    //输出            
    int sum = dp[cols-1];
    
    delete[] dp;
    
    return sum;
    }

二维网格与路径2 最短路径

一个 m x n 的二维网格,从左上角移动到右下角,求最短路径长度,每次只能向右或向下移动一格。
  1. 定义动态规划数组元素:dp[ i ][ j ] 为到达第 i 行第 j 列的最短路径。
  2. 数组元素间的关系:dp[ i ][ j ] = min{ dp[ i – 1 ][ j ] + arc[ i ][ j ] , dp[ i ][ j-1 ] + arc[ i ][ j ] }。
  3. 初始值
    dp[ 0 ][ 0 ] = arc[ 0 ][ 0 ]
    dp[ 0 ][ j ] = dp[ 0 ][ j – 1 ] + arc[ 0 ][ j ]
    dp[ i ][ 0 ] = dp[ i – 1 ][ 0 ] + arc[ i ][ 0 ]

    int unique_path_shortest(int** map, int rows, int cols )
    {
    //定义与初始化
    int** dp = new int*[rows];
    for(int i=0;i<rows;i++)
    {
        dp[i] = new int[cols];
        memset(dp[i],0,cols); 
    }
    
    dp[0][0] = map[0][0];   
    for(int i=1;i<cols;i++)
        dp[0][i] = dp[0][i-1] + map[0][i];
    for(int i=1;i<rows;i++)
        dp[i][0] = dp[i-1][0] + map[i][0];
    //推算
    for(int i=1;i<rows;i++)
        for(int j=1;j<cols;j++)
            dp[i][j] = std::min(dp[i-1][j] + map[i][j] , dp[i][j-1] + map[i][j]);
    //输出
    int sum_shortest = dp[rows-1][cols-1];
    
    for(int i=0;i<rows;i++)
        delete[] dp[i];
    delete[] dp;
    
    return sum_shortest;
    }
  4. 优化空间复杂度:只保存一行的记录,然后不断迭代

    int unique_path_shortest_space(int** map, int rows, int cols )
    {
    //定义与初始化
    int* dp = new int[cols];
    dp[0] = map[0][0];  
    for(int i=1;i<cols;i++)
        dp[i] = dp[i-1] + map[0][i];
    //推算
    for(int i=1;i<rows;i++)
    {
        dp[0] = dp[0] + map[i][0];
        for(int j=1;j<cols;j++)
            dp[j] = std::min(dp[j] + map[i][j] , dp[j-1] + map[i][j]);
    }
    //输出
    int sum_shortest = dp[cols-1];
    
    delete[] dp;
    
    return sum_shortest;
    }

最长递增子序列(LIS)

最长递增子序列(LIS)

2、排序

排序 插入排序 冒泡排序 归并排序 快速排序
基本思想 自第二个记录开始,有序前插 相邻元素比较,逆序交换,逐遍把最大值移至最后 相邻两个/多个顺序表合并,初始每个元素都是一个顺序表 两侧依次扫描,将轴值移至正确位置
时间复杂度 n~n^2 n~n^2 nlbn nlbn~n^2
平均时间复杂度 n^2 n^2 nlbn nlbn
空间复杂度 1 1 n lbn~n
应用/优化 适用于元素数量少,且基本有序,改进:分割,分别直插,基本有序后全体直插 双向起泡 相邻-一趟-递归 递归次数-空间复杂度

快速排序

将待排数组不断分成两部分,分别排序

int partition(int arr[], int start, int end)
{
    int mid = arr[start];
    int i = start;
    int j = end;

    while(i<j){
        //右侧扫描,将小于轴值的元素移到左侧
        while(i<j && arr[j]>mid)
            j--;
        if(i<j){
            arr[i] = arr[j];
            i++;
        }

        //左侧扫描,将大于轴值的元素移到右侧
        while(i<j && arr[i]<mid)
            i++;
        if(i<j){
            arr[j] = arr[i];
            j--;
        }
    }
    arr[i] = mid;
    return i;
}

void quick_sort(int arr[], int start, int end)
{
    int mid_index = partition(arr, start, end);
    if(start < mid_index)
        quick_sort(arr, start, mid_index-1);
    if(mid_index < end)
        quick_sort(arr, mid_index+1,end);
}

3、查找

二分查找

复杂度 lgn

bool binary_search(int arr[], int length, int num)
{
    int start = 0;
    int end = length-1;
    int mid = (start+end)/2;

    while(start<=end)
    {
        if(arr[mid]==num)
            return true;
        else if(arr[mid]>num)
            end = mid - 1;
        else
            start = mid + 1;

        mid = (start+end)/2;
    }

    return false;
}

二叉排序树

复杂度 lgn~n

myNode* searchSortBiTree(myNode* root, int k)
{
    if(root == nullptr)
        return nullptr;
    else if(root->data == k)
        return root;
    else if(root->data > k)
        return searchSortBiTree(root->leftChild, k);
    else
        return searchSortBiTree(root->rightChild, k);
}

4、二叉树

struct myNode
{
    int data;
    myNode* leftChild;
    myNode* rightChild;
    int flag;
};

树的构造与释放

按层序遍历的方式构造。

myNode* createBiTree(int values[], int length)
{
    queue<myNode*> nodes;
    int cnt = 0;

    myNode* n = new myNode;
    n->data = values[0];
    nodes.push(n);
    cnt++;

    while(cnt<length)
    {
        myNode* top = nodes.front();
        nodes.pop();

        myNode* left_n = new myNode;
        left_n->data = values[cnt];

        nodes.push(left_n);
        top->leftChild = left_n;
        cnt++;

        if(cnt==length)
            break;

        myNode* right_n = new myNode;
        right_n->data = values[cnt];
        nodes.push(right_n);
        top->rightChild = right_n;
        cnt++;
    }

    return n;
}

void releaseBiTree(myNode* n) 
{
    /*按照后序遍历的顺序释放二叉树*/
    if(n != NULL) 
    {                  
        releaseBiTree(n->leftChild); /*释放左子树*/
        releaseBiTree(n->rightChild); /*释放右子树*/
        delete n; /*删除根结点*/
    }  
}

前序遍历

// 非递归:栈
void frontPrintBiTree(myNode* root)
{
    stack<myNode*> nodes;
    myNode* n = root;

    while(n != nullptr || nodes.empty()!=1)
    {
        while(n != nullptr)
        {
            nodes.push(n);
            cout<<n->data<<" ";
            n = n->leftChild;
        }

        if(nodes.empty()!=1)
        {
            n = nodes.top();
            nodes.pop();
            n = n->rightChild;
        }
    }
}

// 递归
void frontPrintBiTree_recursion(myNode* root) 
{
    if(root == NULL) /*递归调用的边界条件*/
        return;
    else 
    {       
        cout<<root->data<<" "; /*访问根结点*/
        frontPrintBiTree_recursion(root->leftChild); /*前序递归遍历左子树*/
        frontPrintBiTree_recursion(root->rightChild); /*前序递归遍历右子树*/
    }
}

中序遍历

// 非递归:栈
void middlePrintBiTree(myNode* root)
{
    stack<myNode*> nodes;
    myNode* n = root;

    while(n != nullptr || nodes.empty()!=1)
    {
        while(n != nullptr)
        {
            nodes.push(n);
            n = n->leftChild;
        }

        if(nodes.empty()!=1)
        {
            n = nodes.top();
            nodes.pop();
            cout<<n->data<<" ";
            n = n->rightChild;
        }
    }
}

// 递归
void middlePrintBiTree_recursion(myNode* root) 
{
    if(root == NULL) /*递归调用的边界条件*/
        return;
    else 
    {       
        middlePrintBiTree_recursion(root->leftChild); /*前序递归遍历左子树*/
        cout<<root->data<<" "; /*访问根结点*/
        middlePrintBiTree_recursion(root->rightChild); /*前序递归遍历右子树*/
    }
}

后序遍历

void backPrintBiTree(myNode* root)
{
    stack<myNode*> nodes;
    myNode* n = root;

    while(n!=nullptr || nodes.empty()!=1)
    {
        while(n != nullptr)
        {
            n->flag = 1;
            nodes.push(n);
            n = n->leftChild;
        }

        while(nodes.empty()!=1 && nodes.top()->flag==2)
        {
            n = nodes.top();
            nodes.pop();
            cout<<n->data<<" ";

            if(n==root)
                return;
        }

        if(nodes.empty()!=1)
        {
            n = nodes.top();
            n->flag = 2;
            n = n->rightChild;
        }
    }
}

// 递归
void backPrintBiTree_recursion(myNode* root) 
{
    if(root == NULL) /*递归调用的边界条件*/
        return;
    else 
    {       
        backPrintBiTree_recursion(root->leftChild); /*前序递归遍历左子树*/
        backPrintBiTree_recursion(root->rightChild); /*前序递归遍历右子树*/
        cout<<root->data<<" "; /*访问根结点*/
    }
}

层序遍历

void levelPrintBiTree(myNode* root)
{
    queue<myNode*> nodes;
    myNode* n = root;
    nodes.push(n);

    while(nodes.empty()!=1)
    {
        n = nodes.top();
        nodes.pop();
        cout<<n->data<<" ";
        if(n->leftChild != nullptr)
            nodes.push(n->leftChild);
        if(n->rightChild != nullptr)
            nodes.push(n->rightChild);
    }
}

Z字遍历

void ZPrintBiTree(myNode* root)
{
    queue<myNode*> nodes_que;
    stack<myNode*> nodes_sta;
    int line = 1;
    int cnt = 0;

    queue<myNode*> nodes;
    myNode* n = root;
    nodes.push(n);

    while(nodes.empty()!=1)
    {
        //层序遍历
        n = nodes.front();
        nodes.pop();
        if(n->leftChild != nullptr)
            nodes.push(n->leftChild);
        if(n->rightChild != nullptr)
            nodes.push(n->rightChild);

        //Z字遍历
        cnt++;
        if(line%2 == 1)
        {
            nodes_que.push(n);
            if(cnt==pow(2,line-1))
            {
                line++;
                cnt=0;
                while(nodes_que.empty()!=1)
                {
                    cout<<nodes_que.front()->data<<" ";
                    nodes_que.pop();
                }
            }
        }
        else
        {
            nodes_sta.push(n);
            if(cnt==pow(2,line-1))
            {
                line++;
                cnt=0;
                while(nodes_sta.empty()!=1)
                {
                    cout<<nodes_sta.top()->data<<" ";
                    nodes_sta.pop();
                }
            }
        }       
    }
    //没打印的打印完
    while(nodes_que.empty()!=1)
    {
        cout<<nodes_que.front()->data<<" ";
        nodes_que.pop();
    }
    while(nodes_sta.empty()!=1)
    {
        cout<<nodes_sta.top()->data<<" ";
        nodes_sta.pop();
    }
}

重建二叉树:前序+中序

myNode* createBiTreeByFrontMid(int* frontValues, int* midValues, int length)
{
    if(length==0)//只针对第一次使用
        return nullptr;

    myNode* root = new myNode;//确定这一段的根节点,然后划分左右两子树
    root->data = frontValues[0];
    root->leftChild = nullptr;
    root->rightChild = nullptr;

    int i;
    for(i=0;i<length && midValues[i]!=frontValues[0];i++)
        ;

    int leftLength = i;
    int rightLength = length-i-1;

    if(leftLength>0)
        root->leftChild = createBiTreeByFrontMid(&frontValues[1], &midValues[0], leftLength);
    if(rightLength>0)
        root->rightChild = createBiTreeByFrontMid(&frontValues[leftLength+1], &midValues[leftLength+1], rightLength);

    return root;
}

重建二叉树:中序+后序

myNode* createBiTreeByBackMid(int* backValues, int* midValues, int length)
{
    if(length==0)//只针对第一次使用
        return nullptr;

    myNode* root = new myNode;//确定这一段的根节点,然后划分左右两子树
    root->data = backValues[length-1];
    root->leftChild = nullptr;
    root->rightChild = nullptr;

    int i;
    for(i=0;i<length && midValues[i]!=backValues[length-1];i++)
        ;

    int leftLength = i;
    int rightLength = length-i-1;

    if(leftLength>0)
        root->leftChild = createBiTreeByBackMid(&backValues[0], &midValues[0], leftLength);
    if(rightLength>0)
        root->rightChild = createBiTreeByBackMid(&backValues[leftLength], &midValues[leftLength+1], rightLength);

    return root;
}

二叉排序树

二叉排序树:空树或非空树,非空树满足左子树所有节点小于根节点,右子树所有节点大于根节点,左右子树又是二叉排序树。

插入

void insertSortBiTree(myNode* &root, myNode* newNode)
{
    if(root==nullptr)
        root = newNode;
    else if(root->data > newNode->data)
        insertSortBiTree(root->leftChild, newNode);
    else
        insertSortBiTree(root->rightChild, newNode);
}

构造

myNode* createSortBiTree(int data[], int n)
{
    myNode* root = nullptr;
    myNode* newNode;
    for(int i=0;i<n;i++)
    {
        newNode = new myNode;
        newNode->data = data[i];
        newNode->leftChild = nullptr;
        newNode->rightChild = nullptr;
        insertSortBiTree(root, newNode);
    }
    return root;
}

5、队列与栈

两个队列实现栈

class myStack
{
    private:
        queue<int> que1;
        queue<int> que2;

    public:
        myStack();
        ~myStack(){};

        void push(int);
        void pop();
        int top();
};

myStack::myStack(){}
void myStack::push(int data)//在非空的队列末尾添加新元素
{
    if(que1.empty())
        que2.push(data);
    else
        que1.push(data);
}

void myStack::pop()//将非空队列元素转移到空队列,留最后一个弹出
{
    if(que1.empty() && que2.empty())
        return ;

    if(que1.empty())
    {
        while(que2.size()!=1)
        {
            que1.push(que2.front());
            que2.pop();
        }
        que2.pop();
    }
    else
    {
        while(que1.size()!=1)
        {
            que2.push(que1.front());
            que1.pop();
        }
        que1.pop();
    }   
}

int myStack::top()//将非空队列元素转移到空队列,最后一个输出
{
    if(que1.empty() && que2.empty())
        return -1;

    int out;
    if(que1.empty())
    {
        while(que2.size()!=1)
        {
            que1.push(que2.front());
            que2.pop();
        }
        out = que2.front();
        que1.push(out);
        que2.pop();
    }
    else
    {
        while(que1.size()!=1)
        {
            que2.push(que1.front());
            que1.pop();
        }
        out = que1.front();
        que2.push(out);
        que1.pop();
    }
        return out;
}

两个栈实现队列

class myQueue
{
    private:
        stack<int> sta1;//存
        stack<int> sta2;//取

    public:
        myQueue();
        ~myQueue(){};

        void push(int);
        void pop();
        int front();
};

myQueue::myQueue(){}
void myQueue::push(int data)
{
    sta1.push(data);
}

void myQueue::pop()
{
    if(sta1.empty())
        return ;

    while(sta1.size()!=1)
    {   
        sta2.push(sta1.top());
        sta1.pop();
    }

    sta1.pop(); 

    while(sta2.size()!=0)   
    {
        sta1.push(sta2.top());
        sta2.pop();
    }   
}
int myQueue::front()
{
    if(sta1.empty())
        return -1;

    while(sta1.size()!=1)
    {   
        sta2.push(sta1.top());
        sta1.pop();
    }

    int out = sta1.top();
    sta2.push(out);
    sta1.pop(); 

    while(sta2.size()!=0)   
    {
        sta1.push(sta2.top());
        sta2.pop();
    }   

    return out;
}

循环队列*

相对于直线队列来讲,直线队列在元素出队后,头指针向后移动,导致删除元素后的空间无法在利用,即使元素个数小于空间大小,依然无法再进行插入,即所谓的“假上溢”。当变成循环队列之后,删除元素后的空间仍然可以利用,最大限度的利用空间。

6、图

深度优先遍历

void DFS_recursion(int curVertex, int vertexNum, vector<int>* arc, vector<int>* visited)
{
    (*visited)[curVertex] = 1;              //标记该次遍历的顶点
    cout<<curVertex<<" ";
    for(int i=0; i<vertexNum;i++)       //遍历该顶点连接的其他顶点
        if((*visited)[i]==0 && (*arc)[curVertex*vertexNum+i]==1)
            DFS_recursion(i, vertexNum, arc, visited);  
}

void DFS(int vertexNum, vector<int>* arc)
{
//初始化所有顶点未被遍历
    vector<int> visited; 
    for(int i=0;i<vertexNum; i++)
        visited.push_back(0);

//深度遍历
    for(int i=0; i<vertexNum; i++)//从第i个顶点开始的一个连通分量
    {
        if(visited[i]==0)
            DFS_recursion(i, vertexNum, arc, &visited);
    }
}

广度优先遍历

void BFS(int vertexNum, vector<int>* arc)
{
//初始化所有顶点未被遍历
    vector<int> visited; 
    for(int i=0;i<vertexNum; i++)
        visited.push_back(0);

//广度遍历
    queue<int> q;
    for(int i=0;i<vertexNum; i++)//从第i个顶点开始的一个连通分量
    {
        if(visited[i]==0)
        {
            q.push(i);
            visited[i]=1;

            while(q.empty()!=1)
            {
                int v = q.front();
                q.pop();
                cout<<v<<" ";

                for(int j=0; j<vertexNum; j++)
                    if(visited[j]==0 && (*arc)[v*vertexNum+j]==1)
                    {
                        q.push(j);
                        visited[j]=1;
                    }
            }
        }
    }
}

最小生成树

n个城市之间铺设电缆,目标使得任意两个城市互通,不同城市之间铺设电缆代价不同,求代价最小的铺设方法。

Prim

向最小生成树添加点。

void Prim(int vertexNum, vector<int>* arc)
{
//初始化候选最短边集
    vector<int> connectCost;    //候选最短边集:候选顶点到正式顶点的最小代价 代价为0,即选为正式顶点
    vector<int> connectVertex;  //候选最短边集:最小代价对应的正式顶点        找内推?

    for(int i=0;i<vertexNum;i++)
    {
        connectCost.push_back((*arc)[0*vertexNum + i]); //默认到自身的代价为0,因此0号顶点初始化即选为正式顶点
        connectVertex.push_back(0);
    }

//最小生成树:每次加入一个正式顶点,并更新候选最短边集
    for(int m=1;m<vertexNum;m++)
    {
        //遍历最短边集,找到最最短的一个
        int k=0;
        for(int i=0;i<vertexNum;i++)
            if(connectCost[i]!=0)
            {
                k=i; break;
            }

        for(int i=0;i<vertexNum;i++)
            if(connectCost[i]!=0 && connectCost[i]<connectCost[k])
                k=i;

        if(connectCost[k]==0)//找一圈发现均已添加到正式顶点中,则退出
            return ;

        cout<<"("<<connectVertex[k]<<"->"<<k<<")-cost-"<<connectCost[k]<<" ";
        //把第k个顶点加入正式顶点中
        connectCost[k]=0;   
        //看看剩下的候选节点是不是到k的代价更小
        for(int i=0;i<vertexNum;i++)
            if(connectCost[i]!=0 && connectCost[i]>(*arc)[k*vertexNum + i])
            {
                connectCost[i] = (*arc)[k*vertexNum + i];   
                connectVertex[i] = k;
            }
    }   
}

Kruscal

向最小生成树添加边。

struct Edge
{
    int vertexA;
    int vertexB;
    int cost;
};
bool cmp(const Edge &a,const Edge &b)
{
    return (a.cost) < (b.cost);
}
int find_parent(int v, vector<int> &parents)
{
    return parents[v]==v ? v : find_parent(parents[v], parents);//递归找到顶层双亲
}
void Kruscal(int vertexNum, vector<int>* arc)
{
//数据准备
    vector<Edge> edges;
    for(int i=0;i<vertexNum;i++)
        for(int j=i+1;j<vertexNum;j++)
            if((*arc)[i*vertexNum + j] < 100)
            {
                Edge edge;
                edge.vertexA = i;
                edge.vertexB = j;
                edge.cost = (*arc)[i*vertexNum + j];
                edges.push_back(edge);
            }
//Kruscal
    //边集数组初始化:升序排列
    sort(edges.begin(),edges.end(),cmp);

    //顶层双亲初始化:用于判断某一边的两顶点是否在同一个连通分量
    vector<int> parents;
    for(int i=0;i<vertexNum;i++)
        parents.push_back(i);//初始化每个顶点自成一个连通分量,其双亲为自身

    //最小生成树:每次添加一条边
    for(int i=0;i<edges.size();i++)
    {
        //拿出一条边,判断是否在同一个连通分量:判断顶层双亲是否相同
        Edge e = edges[i];
        if(find_parent(e.vertexA, parents) != find_parent(e.vertexB, parents))
        {
            parents[find_parent(e.vertexB, parents)]=find_parent(e.vertexA, parents);
            cout<<"("<<e.vertexA<<"->"<<e.vertexB<<")-cost-"<<e.cost<<" ";
        }
    }
}

7、幷查集

题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

  1. 幷查集的两种操作:查询是否在同一集合,合并不同集合
  2. 幷查集的思想:每个集合确定一个代表元素,形成一个树状结构,代表元素为根节点,其他元素为子节点。集合的合并就是一个集合的根节点成为另一个集合的子节点。集合的查询就是不断找父节点,直到找到根节点,判断根节点是否相同。
  3. 幷查集应用:最小生成树的Kruscal方法。
  4. 幷查集的初始化,合并和查询
    int parent[NMAX]
    void init(int n)    //初始化为本身
    {
    for(int i=0;i<n;i++)
        parent[i]=i;
    }
    int find(int i)
    {
    return parent[i]==i ? i : find(parent[i]); //不断向上查询父节点,直到所属结合的根节点
    }
    int merge(int i, int j)
    {
    parent[find(i)] = find(j);//把i所属集合的根节点,变成j所属集合根节点的子节点
    }
  5. 压缩:查询过程中将其父节点换成根节点,减少递归次数,因为只在查询时压缩,因此未必每个集合除了根节点,全是叶子节点
    int find(int i)
    {
    return parent[i]==i ? i : ( parent[i] = find(parent[i]) ); //查询过程中将其父节点换成根节点
    }
  6. 按秩合并:把简单树合并到复杂树
    算法学习笔记(1) : 并查集

8、字符串

有效字符串

在这里插入图片描述
一个看着不错的答案

bool checkValidString(String s) {
    int low = 0; // 表示*号都作为右括号时,左括号的数量
    int hight = 0; // 表示*都作为左括号时,左括号的数量
    // 如果hight < 0 说明*都作为左括号了,还是不够抵消不了右括号
    // 如果low > 0 说明*号都作为右括号了,还是抵消不了括号的值
    for (int i=0; i<s.length();i++){
        char tmp = s.charAt(i);
        if (tmp == '(') {
            low++;
            hight++;
        } 
        else if (tmp == ')') {
            if (low > 0) {
                low--;
            }
            hight--;
        } else if (tmp == '*') {
            if (low > 0) {
                low--;
            }
            hight++;
        }

        if (hight < 0) {
            return false;
        }
    }
    return low == 0;
}

9、数组

差分数组 前缀和

在这里插入图片描述

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {

        vector<int> data;
        for(int i=0; i<n;i++)
            data.push_back(0);

        for(auto i : bookings)
        {
            data[i[0]-1]+=i[2];
            if(i[1]<n)
                data[i[1]]-=i[2];
        }

        for(int i=1; i<n;i++)
            data[i]+=data[i-1];

        return data;
    }
};

测试

代码

int main()
{
//动态规划
    int a[]{1,-5,8,3,-4,15,-8};
    std::cout<<"max_successive_sub_serial_sum: "<<max_successive_sub_serial_sum(a,7)<<std::endl;
    std::cout<<"max_successive_sub_serial_sum_space: "<<max_successive_sub_serial_sum_space(a,7)<<std::endl;

    std::cout<<"unique_path: "<<unique_path(3,7)<<std::endl;
    std::cout<<"unique_path_space: "<<unique_path_space(3,7)<<std::endl;

    int rows=3, cols=3;
    int** map = new int*[rows];
    for(int i=0;i<rows;i++)
    {
        map[i] = new int[cols];
        for(int j=0;j<cols;j++)
            map[i][j]=j;
    }
    std::cout<<"unique_path_shortest: "<<unique_path_shortest(map, rows, cols)<<std::endl;
    std::cout<<"unique_path_shortest_space: "<<unique_path_shortest_space(map, rows, cols)<<std::endl;
//排序    
    int arr[8]{8,7,6,5,4,4,2,1};
    quick_sort(arr,0,7);
    std:cout<<"quick_sort: ";
    for(int i=0;i<8;i++)
        std::cout<<arr[i]<<" ";
    std::cout<<std::endl;
//查找    
    int arr1[9]{8,7,6,5,4,4,2,1,11};
    std::cout<<"binary_search: "<<binary_search(arr, 8,5)<<std::endl;
    std::cout<<"binary_search: "<<binary_search(arr1, 9,3)<<std::endl;

//二叉树
    int arr2[9]{8,7,6,5,4,4,2,1,11};
    myNode* root = createBiTree(arr2, 9);
    cout<<"frontPrintBiTree: ";
    frontPrintBiTree(root);
    cout<<endl<<"middlePrintBiTree: ";
    middlePrintBiTree(root);
    cout<<endl<<"backPrintBiTree: ";
    backPrintBiTree(root);
    cout<<endl<<"frontPrintBiTree_recursion: ";
    frontPrintBiTree_recursion(root);
    cout<<endl<<"middlePrintBiTree_recursion: ";
    middlePrintBiTree_recursion(root);
    cout<<endl<<"backPrintBiTree_recursion: ";
    backPrintBiTree_recursion(root);
    cout<<endl<<"levelPrintBiTree: ";
    levelPrintBiTree(root);
    cout<<endl<<"ZPrintBiTree: ";
    ZPrintBiTree(root);
    releaseBiTree(root);

    int arr2_front[9]{8,7,5,1,11,4,6,4,2};
    int arr2_mid[9]{1,5,11,7,4,8,4,6,2};
    int arr2_back[9]{1,11,5,4,7,4,2,6,8};
    myNode* root1 = createBiTreeByFrontMid(arr2_front, arr2_mid, 9);
    cout<<endl<<"createBiTreeByFrontMid backPrintBiTree: ";
    backPrintBiTree(root1);
    releaseBiTree(root1);
    myNode* root2 = createBiTreeByBackMid(arr2_back, arr2_mid, 9);
    cout<<endl<<"createBiTreeByBackMid frontPrintBiTree: ";
    frontPrintBiTree(root2);
    releaseBiTree(root2);
    cout<<endl;

//队列与栈
    myStack s;
    s.push(1);
    s.push(2);
    s.push(3);
    s.push(4);
    s.push(5);
    cout<<"s.top():"<<s.top()<<endl;
    s.pop();
    cout<<"s.top() after pop():"<<s.top()<<endl;

    myQueue q;
    q.push(1);
    q.push(2);
    q.push(3);
    q.push(4);
    q.push(5);
    cout<<"q.front():"<<q.front()<<endl;
    q.pop();
    cout<<"q.front() after pop():"<<q.front()<<endl;

//图
    int vertexNum = 10;
                  //0 1 2 3 4  5 6 7 8 9 无向图 两个连通分量 
    int arc_a[10][10]={0,0,1,0,1, 1,0,0,0,0, 
               0,0,0,1,0, 0,0,0,0,0,
               1,0,0,0,0, 0,0,0,0,0,
               0,1,0,0,0, 0,0,0,0,0,
               1,0,0,0,0, 0,0,0,1,0,

               1,0,0,0,0, 0,1,1,0,0,
               0,0,0,0,0, 1,0,0,0,0,
               0,0,0,0,0, 1,0,0,0,0,
               0,0,0,0,1, 0,0,0,0,1,
               0,0,0,0,0, 0,0,0,1,0}; 

    vector<int> arc;
    for(int i=0;i<vertexNum;i++)
        for(int j=0;j<vertexNum;j++)
            arc.push_back(arc_a[i][j]);
    cout<<"DFS: ";
    DFS(vertexNum, &arc);   
    cout<<endl<<"BFS: ";
    BFS(vertexNum, &arc);   
    cout<<endl;

    vertexNum = 6;
               //0 1 2 3 4  5 6 7 8 9 无向连通图
    int arc_b[6][6]={0, 4,  100,    100,    5,  2,   
                  4,    0,  2,  100,    100,    1,
                 100,   2,  0,  10, 100,    3,
                 100,   100,    10, 0,  1,  4,
                 5, 100,    100,    1,  0,  8,
                 2, 1,  3,  4,  8,  0}; 
    arc.clear();
    for(int i=0;i<vertexNum;i++)
        for(int j=0;j<vertexNum;j++)
            arc.push_back(arc_b[i][j]);
    cout<<"Prim:";
    Prim(vertexNum, &arc);
    cout<<endl<<"Kruscal:";
    Kruscal(vertexNum, &arc);
    cout<<endl;

//二叉排序树
    int sortBiTree[]{80,40,70,90,100,15,81,77,89,96,1};
    myNode* sortRoot = createSortBiTree(sortBiTree, 11);
    cout<<"createSortBiTree frontPrintBiTree: ";
    frontPrintBiTree(sortRoot);
    cout<<endl;
    myNode* getNode = searchSortBiTree(sortRoot, 96);
    cout<<"searchSortBiTree 96:"<<(getNode!=nullptr)<<endl;
    getNode = searchSortBiTree(sortRoot, 77);
    cout<<"searchSortBiTree 77:"<<(getNode!=nullptr)<<endl;
    getNode = searchSortBiTree(sortRoot, 2);
    cout<<"searchSortBiTree 2:"<<(getNode!=nullptr)<<endl;
    releaseBiTree(sortRoot);    
    return 0;
}

输出

max_successive_sub_serial_sum: 22
max_successive_sub_serial_sum_space: 22
unique_path: 28
unique_path_space: 28
unique_path_shortest: 3
unique_path_shortest_space: 3
quick_sort: 1 2 4 4 5 6 7 8 
binary_search: 1
binary_search: 0
frontPrintBiTree: 8 7 5 1 11 4 6 4 2 
middlePrintBiTree: 1 5 11 7 4 8 4 6 2 
backPrintBiTree: 1 11 5 4 7 4 2 6 8 
frontPrintBiTree_recursion: 8 7 5 1 11 4 6 4 2 
middlePrintBiTree_recursion: 1 5 11 7 4 8 4 6 2 
backPrintBiTree_recursion: 1 11 5 4 7 4 2 6 8 
levelPrintBiTree: 8 7 6 5 4 4 2 1 11 
ZPrintBiTree: 8 6 7 5 4 4 2 11 1 
createBiTreeByFrontMid backPrintBiTree: 1 11 5 4 7 4 2 6 8 
createBiTreeByBackMid frontPrintBiTree: 8 7 5 1 11 4 6 4 2 
s.top():5
s.top() after pop():4
q.front():1
q.front() after pop():2
DFS: 0 2 4 8 9 5 6 7 1 3 
BFS: 0 2 4 5 8 6 7 9 1 3 
Prim:(0->5)-cost-2 (5->1)-cost-1 (1->2)-cost-2 (5->3)-cost-4 (3->4)-cost-1 
Kruscal:(1->5)-cost-1 (3->4)-cost-1 (0->5)-cost-2 (1->2)-cost-2 (3->5)-cost-4
createSortBiTree frontPrintBiTree: 80 40 15 1 70 77 90 81 89 100 96 
searchSortBiTree 96:1
searchSortBiTree 77:1
searchSortBiTree 2:0