C++笔记:容器适配器:优先级队列(priority_queue)模拟实现

文章目录

    • 框架
    • (constructor)
    • size()、empty()、top()
    • push()
      • 声明
      • 参数
      • 实现
      • 代码
    • pop()
      • 声明
      • 实现
      • 代码
    • 仿函数与函数指针
      • 仿函数的定义
      • 仿函数实现回调
      • 函数指针实现回调
      • adjust_up 和 adjust_down 的改进
  • 完整代码

容器适配器(Container Adapter)是一种 C++ 中的抽象数据类型,它提供了一种在指定底层容器基础上进行封装,以实现特定功能的方式。容器适配器并不是独立的容器类型,而是建立在其他容器之上的封装,通过提供不同的接口或限制来满足特定需求。

priority_queue(优先级队列)就是一个容器适配器,是数据结构上的堆(Heap)的实现,库中的声明如下:

template <class T, 
	class Container = vector<T>,
	class Compare = less<typename Container::value_type> > 
 class priority_queue;
  • T:指数据元素的类型。
  • Container :指存储元素的内部基础容器对象的类型,默认以 vector 进行适配。
  • Compare :仿函数对象的类型。(什么是仿函数后面会说)

基础容器可以是库里有的,也可以是自己实现的,但都应当满足以下要求:

  1. 要求应可通过随机访问迭代器访问
  2. 支持以下内容 operations:
    • empty()
    • size()
    • front()
    • push_back()
    • pop_back()

框架

本次模拟实现只是,简单的模拟实现,旨在加深对 priority_queue 的理解和了解和使用仿函数,主要参考 C++98 版本的 priority_queue。进行模拟实现。

priority_queue 类的大致框架如下:

template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:
	// priority_queue 该提供以下接口
	priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare());
	template <class InputIterator>
    priority_queue(InputIterator first, InputIterator last);

	bool empty() const;
	size_t size() const;
	const T& top() const;
	void push(const T& x);
	void pop();

private:
	void adjust_up(size_t child);
	void adjust_down(size_t parent);

private:
    Container _c;
    Compare _comp;
};

(constructor)

C++98版本的 priority_queue 的构造函数重载有两个版本,一个是全缺省的默认构造函数,另一个是迭代器区间构造函数。

// 全缺省默认构造
priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare())
    : _c(ctnr)
    , _comp(comp)
{}

// 迭代器范围区间构造
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
    // 先调用 Container 对象的 range constructor
    : _c(first, last)
{
    // 从最后一个非叶子结点开始向下调整成堆结构
    int count = size();
    int root = ((count - 2) >> 1);
    for (; root >= 0; --root)
    {
        adjust_down(root);
    }
}

size()、empty()、top()

这三个不是重点,且实现起来比较简单。

size_t size() const
{
    return _c.size();
}

bool empty() const
{
    return _c.empty();
}

// 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性
const T& top() const
{
    return _c.front();
}

push()

声明

void push (const T& val);

参数

形参 val 是待插入对象
val 类型是 const T&,引用传参是为了降低传参消耗。

实现

  1. 先将 val 插入到堆的末尾,即最后一个孩子之后。
  2. 插入之后如果堆的性质遭到破坏,将新插入结点顺着双亲往上调整到合适位置。

代码

void push(const T& x)
{
	// 1. 对象插入数据
	_c.push_back(x);
	
	// 2. 向上调整
	adjust_up(_c.size() - 1);
}

// 向上调整算法
void adjust_up(size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
	    if (_c[parent] < _c[child])
	    {
	        swap(_c[parent], _c[child]);
	        child = parent;
	        parent = (child - 1) / 2;
	    }
	    else
	    {
	        break;
	    }
	}
}

pop()

声明

void pop();

实现

  1. 将堆顶元素与堆中的最后一个元素进行交换。
  2. 删除堆中最后一个元素。
  3. 从堆顶元素位置开始向下调整到满足堆特性为止。

代码

void pop()
{
	if (empty())
		return;

    // 交换堆顶和末尾
    swap(_c[0], _c[_c.size() - 1]);
    
    // 删除末尾位置元素
    _c.pop_back();

    // 堆顶位置向下调整
    adjust_down(0);
}

// 向下调整算法
void adjust_down(size_t parent)
{
    size_t child = (parent * 2) + 1;
    while (child < _c.size())
    {
        if (child + 1 < _c.size() && _c[child] < _c[child + 1])
    	{
        	child += 1;
    	}
    	
        if (_c[parent] < _c[child])
        {
            swap(_c[parent], _c[child]);
            parent = child;
            child = (parent * 2) + 1;
        }
        else
        {
            break;
        }
    }
}

仿函数与函数指针

// adjust_up 
if (_c[parent] < _c[child])

// adjust_down 
if (child + 1 < _c.size() && _c[child] < _c[child + 1])
if (_c[parent] < _c[child])

这几句代码写死了,priority_queue 实现的堆只能是大堆,但是通过直接修改源代码的方式来进行大小堆的切换是不现实的,更推荐的做法是通过回调函数控制比较逻辑,C语言采用函数指针实现,C++更喜欢使用仿函数(函数对象)。

仿函数的定义

仿函数(Functor)是一种行为类似函数的对象,类中重载了函数调用运算符 operator(),通过这种方式,对象就可以像函数一样被调用。

class less
{
public:
	bool operator()(int x, int y)
	{
		return x < y;
	}
};
int main()
{
	// 实例化对象 lessfunc
	less lessfunc;
	// 调用成员函数
	cout << lessfunc(1, 2) << endl;
	cout << lessfunc.operator()(1, 2) << endl;
	
	return 0;
}

乍一看我们会认为lessfunc是函数名,但其实它是一个对象,它通过运算符重载让这个对象能够仿造函数的形式来使用。

仿函数实现回调

现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过使用仿函数回调 less 类对象、greater 类对象中的方法来实现 func() 中比较逻辑的控制,做法如下:

class less
{
public:
	bool operator()(int x, int y)
	{
		return x < y;
	}
};

class greater
{
public:
	bool operator()(int x, int y)
	{
		return x > y;
	}
};

template<class Compare>
class A
{
public:
	// 功能:比较两个int数据的大小,返回比较结果
	void func(int xx, int yy)
	{
		Compare com;
		cout << com(xx, yy) << endl;
	}
};

int main()
{
	A<less> a1;
	a1.func(100, 200);

	A<less> a2;
	a2.func(100, 200);
	
	return 0;
}

函数指针实现回调

现在有一个 A 类,类中有一个成员方法 func() 作用是比较两个整型数据的大小,func() 通过函数指针回调全局函数 lessfc、greaterfc 来控制 func() 的比较逻辑,做法如下:

bool lessfc(int x, int y)
{
	return x < y;
}

bool greaterfc(int x, int y)
{
	return x > y;
}

// A 类回调 lessfc、greater
class A
{
public:
	A(bool(*pf)(int, int))
		:_pf(pf)
	{}

	// 控制 xx,yy 的比较逻辑
	void func(int xx, int yy)
	{
		cout << _pf(xx, yy) << endl;
	}

private:
	bool(*_pf)(int, int);
};

int main()
{
	A a(lessfc);
	// 比较大小
	cout << a.func(100, 200) << endl;
	
	A a(greaterfc);
	// 比较大小
	cout << a.func(100, 200) << endl;
	
	return 0;
}

相较于仿函数实现,函数指针实现有几个缺陷:

  1. 函数指针类型写起来复杂。
  2. 函数指针只能通过函数参数的形式来传递,这就被迫要求实现构造函数,同时还要用一个成员变量来保存传递进来的函数指针,以便使用。

adjust_up 和 adjust_down 的改进

template<class T>
class less
{
public:
    bool operator()(const T& x, const T& y)
    {
        return x < y;
    }
};

template<class T>
class greater
{
public:
    bool operator()(const T& x, const T& y)
    {
        return x > y;
    }
};
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:
	// push()...
	// pop()...
private:
	void adjust_up(size_t child)
	{
	    size_t parent = (child - 1) / 2;
	    while (child > 0)
	    {
	        //if (_c[parent] < _c[child])
	        if (_comp(_c[parent], _c[child]))
	        {
	            swap(_c[parent], _c[child]);
	            child = parent;
	            parent = (child - 1) / 2;
	        }
	        else
	        {
	            break;
	        }
	    }
	}
	
void adjust_down(size_t parent)
{
    size_t child = (parent * 2) + 1;
    while (child < _c.size())
    {
        //if (child + 1 < _c.size() && _c[child] < _c[child + 1])
        if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1]))
        {
            child += 1;
        }

        //if (_c[parent] < _c[child])
        if (_comp(_c[parent], _c[child]))
        {
            swap(_c[parent], _c[child]);
            parent = child;
            child = (parent * 2) + 1;
        }
        else
        {
            break;
        }
    }
}

private:
	Container _c;
	Compare _comp;
};

完整代码

namespace ljh
{
	#include
	
    template<class T>
    class less
    {
    public:
        bool operator()(const T& x, const T& y)
        {
            return x < y;
        }
    };
    
    template<class T>
    class greater
    {
    public:
        bool operator()(const T& x, const T& y)
        {
            return x > y;
        }
    };

    template <class T, class Container = vector<T>, class Compare = less<T> >
    class priority_queue
    {
    public:

        priority_queue(const Container& ctnr = Container(), const Compare& comp = Compare())
            : _c(ctnr)
            , _comp(comp)
        {}

        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
            // 先调用 Container 对象的 range constructor
            : _c(first, last)
        {
            // 从最后一个非叶子结点开始向下调整成堆结构
            int count = size();
            int root = ((count - 2) >> 1);
            for (; root >= 0; --root)
            {
                adjust_down(root);
            }
        }

        bool empty() const
        {
            return _c.empty();
        }

        size_t size() const
        {
            return _c.size();
        }

        // 堆顶数据不可被修改,堆顶元素被修改会破坏堆的特性
        const T& top() const
        {
            return _c.front();
        }

        void push(const T& x)
        {
            // 1. 对象插入数据
            _c.push_back(x);

            // 2. 向上调整
            adjust_up(_c.size() - 1);
        }

        void pop()
        {
            if (empty())
                return;

            // 交换堆顶和末尾
            swap(_c[0], _c[_c.size() - 1]);
            
            // 删除末尾位置元素
            _c.pop_back();

            // 堆顶位置向下调整
            adjust_down(0);
        }

    private:
        void adjust_up(size_t child)
        {
            size_t parent = (child - 1) / 2;
            while (child > 0)
            {
                //if (_c[parent] < _c[child])
                if (_comp(_c[parent], _c[child]))
                {
                    swap(_c[parent], _c[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else
                {
                    break;
                }
            }
        }

        void adjust_down(size_t parent)
        {
            size_t child = (parent * 2) + 1;
            while (child < _c.size())
            {
                //if (child + 1 < _c.size() && _c[child] < _c[child + 1])
                if (child + 1 < _c.size() && _comp(_c[child], _c[child + 1]))
                {
                    child += 1;
                }

                //if (_c[parent] < _c[child])
                if (_comp(_c[parent], _c[child]))
                {
                    swap(_c[parent], _c[child]);
                    parent = child;
                    child = (parent * 2) + 1;
                }
                else
                {
                    break;
                }
            }
        }

    private:
        Container _c;
        Compare _comp;

    };
}

你可能感兴趣的:(c++,笔记,开发语言,数据结构,算法,学习方法,stl)